From b91e280956ef5042d08d745939105e2795c2fae6 Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Fri, 18 Sep 2026 17:13:46 -0400 Subject: [PATCH 01/72] easy ones --- sp.h | 235 +++++++++++++++++++++++++++++++-------- test/fs/atomic.c | 20 ++-- test/fs/create_dir.c | 8 +- test/fs/create_file.c | 14 ++- test/fs/links.c | 14 ++- test/fs/mod_time.c | 30 +++-- test/fs/predicates.c | 22 ++-- test/fs/remove.c | 10 +- test/io/discard.c | 4 +- test/io/file.c | 108 +++++++++--------- test/io/io.h | 7 +- test/io/seeking_reader.c | 14 +-- 12 files changed, 330 insertions(+), 156 deletions(-) diff --git a/sp.h b/sp.h index 4d4375cd..62fe3544 100644 --- a/sp.h +++ b/sp.h @@ -3038,6 +3038,11 @@ typedef enum { SP_FS_PATH_WINDOWS, } sp_fs_path_kind_t; +typedef struct { + sp_sys_fd_t dir; + sp_str_t sub; +} sp_path_t; + typedef struct { sp_str_t path; sp_str_t name; @@ -3093,23 +3098,40 @@ SP_API sp_str_t sp_fs_replace_ext(sp_mem_t mem, sp_str_t path, sp_st SP_API sp_err_t sp_fs_collect(sp_mem_t mem, sp_str_t path, sp_da(sp_fs_entry_t)* out); SP_API sp_err_t sp_fs_collect_recursive(sp_mem_t mem, sp_str_t path, sp_da(sp_fs_entry_t)* out); SP_API bool sp_fs_exists(sp_str_t path); +SP_API bool sp_fs_exists_at(sp_path_t path); SP_API bool sp_fs_is_file(sp_str_t path); +SP_API bool sp_fs_is_file_at(sp_path_t path); SP_API bool sp_fs_is_dir(sp_str_t path); +SP_API bool sp_fs_is_dir_at(sp_path_t path); SP_API bool sp_fs_is_symlink(sp_str_t path); +SP_API bool sp_fs_is_symlink_at(sp_path_t path); SP_API bool sp_fs_is_target_file(sp_str_t path); +SP_API bool sp_fs_is_target_file_at(sp_path_t path); SP_API bool sp_fs_is_target_dir(sp_str_t path); +SP_API bool sp_fs_is_target_dir_at(sp_path_t path); SP_API sp_fs_kind_t sp_fs_get_kind(sp_str_t path); +SP_API sp_fs_kind_t sp_fs_get_kind_at(sp_path_t path); SP_API sp_fs_kind_t sp_fs_get_target_kind(sp_str_t path); +SP_API sp_fs_kind_t sp_fs_get_target_kind_at(sp_path_t path); SP_API sp_tm_epoch_t sp_fs_get_mod_time(sp_str_t path); +SP_API sp_tm_epoch_t sp_fs_get_mod_time_at(sp_path_t path); SP_API sp_err_t sp_fs_create_dir(sp_str_t path); +SP_API sp_err_t sp_fs_create_dir_at(sp_path_t path); SP_API sp_err_t sp_fs_create_file(sp_str_t path); +SP_API sp_err_t sp_fs_create_file_at(sp_path_t path); SP_API sp_err_t sp_fs_create_file_str(sp_str_t path, sp_str_t str); +SP_API sp_err_t sp_fs_create_file_str_at(sp_path_t path, sp_str_t str); SP_API sp_err_t sp_fs_create_file_slice(sp_str_t path, sp_mem_slice_t slice); +SP_API sp_err_t sp_fs_create_file_slice_at(sp_path_t path, sp_mem_slice_t slice); SP_API sp_err_t sp_fs_create_file_cstr(sp_str_t path, const c8* str); +SP_API sp_err_t sp_fs_create_file_cstr_at(sp_path_t path, const c8* str); SP_API sp_err_t sp_fs_remove_dir(sp_str_t path); SP_API sp_err_t sp_fs_remove_file(sp_str_t path); +SP_API sp_err_t sp_fs_remove_file_at(sp_path_t path); SP_API sp_err_t sp_fs_create_hard_link(sp_str_t target, sp_str_t link_path); +SP_API sp_err_t sp_fs_create_hard_link_at(sp_path_t target, sp_path_t link_path); SP_API sp_err_t sp_fs_create_sym_link(sp_str_t target, sp_str_t link_path); +SP_API sp_err_t sp_fs_create_sym_link_at(sp_str_t target, sp_path_t link_path); SP_API sp_err_t sp_fs_link(sp_str_t from, sp_str_t to, sp_fs_link_kind_t kind); SP_API sp_err_t sp_fs_copy(sp_str_t from, sp_str_t to); SP_API sp_err_t sp_fs_copy_into(sp_str_t from, sp_str_t dir); @@ -4634,6 +4656,7 @@ SP_API sp_err_t sp_io_copy_b(sp_io_writer_t* dst, sp_io_reader_t* src, u8* SP_API sp_err_t sp_io_read(sp_io_reader_t* reader, void* ptr, u64 size, u64* bytes_read); SP_API sp_err_t sp_io_read_all(sp_io_reader_t* reader, void* ptr, u64 size, u64* bytes_read); SP_API sp_err_t sp_io_read_file(sp_mem_t mem, sp_str_t path, sp_str_t* content); +SP_API sp_err_t sp_io_read_file_at(sp_mem_t mem, sp_path_t path, sp_str_t* content); SP_API void sp_io_reader_from_mem(sp_io_reader_t* reader, const void* ptr, u64 size); SP_API void sp_io_reader_set_buffer(sp_io_reader_t* reader, u8* buf, u64 capacity); @@ -4663,6 +4686,7 @@ SP_API void sp_io_seeking_reader_from_mem(sp_io_seeking_reader_t* sr, SP_API void sp_io_seeking_reader_from_file_reader(sp_io_seeking_reader_t* sr, sp_io_file_reader_t* fr); SP_API sp_err_t sp_io_file_reader_from_path(sp_io_file_reader_t* r, sp_str_t path); +SP_API sp_err_t sp_io_file_reader_from_path_at(sp_io_file_reader_t* r, sp_path_t path); SP_API sp_err_t sp_io_file_reader_from_file(sp_io_file_reader_t* r, sp_sys_fd_t file, sp_io_close_mode_t mode); SP_API sp_err_t sp_io_file_reader_seek(sp_io_file_reader_t* r, s64 offset, sp_io_whence_t whence, s64* position); SP_API sp_err_t sp_io_file_reader_size(sp_io_file_reader_t* r, u64* size); @@ -4670,6 +4694,7 @@ SP_API sp_err_t sp_io_file_reader_size_force(sp_io_file_reader_t* r, u64* SP_API sp_err_t sp_io_file_reader_close(sp_io_file_reader_t* r); SP_API sp_err_t sp_io_file_writer_from_path(sp_io_file_writer_t* w, sp_str_t path); +SP_API sp_err_t sp_io_file_writer_from_path_at(sp_io_file_writer_t* w, sp_path_t path); SP_API sp_err_t sp_io_file_writer_from_fd(sp_io_file_writer_t* w, sp_sys_fd_t fd, sp_io_close_mode_t close_mode); SP_API sp_err_t sp_io_file_writer_seek(sp_io_file_writer_t* w, s64 offset, sp_io_whence_t whence, s64* position); SP_API sp_err_t sp_io_file_writer_size(sp_io_file_writer_t* w, u64* size); @@ -4709,15 +4734,20 @@ typedef struct { } sp_fs_atomic_t; SP_API sp_err_t sp_fs_atomic_open(sp_fs_atomic_t* af, sp_str_t path); -SP_API sp_err_t sp_fs_atomic_open_at(sp_fs_atomic_t* af, sp_sys_fd_t dir, sp_str_t path); +SP_API sp_err_t sp_fs_atomic_open_at(sp_fs_atomic_t* af, sp_path_t path); SP_API sp_err_t sp_fs_atomic_open_staged(sp_fs_atomic_t* af, sp_str_t path, sp_str_t staging); +SP_API sp_err_t sp_fs_atomic_open_staged_at(sp_fs_atomic_t* af, sp_path_t path, sp_str_t staging); SP_API sp_io_writer_t* sp_fs_atomic_writer(sp_fs_atomic_t* af); SP_API sp_err_t sp_fs_atomic_commit(sp_fs_atomic_t* af, sp_fs_atomic_mode_t mode); SP_API sp_err_t sp_fs_atomic_abort(sp_fs_atomic_t* af); SP_API sp_err_t sp_fs_write_atomic(sp_str_t path, sp_str_t str); +SP_API sp_err_t sp_fs_write_atomic_at(sp_path_t path, sp_str_t str); SP_API sp_err_t sp_fs_write_atomic_slice(sp_str_t path, sp_mem_slice_t slice); +SP_API sp_err_t sp_fs_write_atomic_slice_at(sp_path_t path, sp_mem_slice_t slice); SP_API sp_err_t sp_fs_write_atomic_slice_staged(sp_str_t path, sp_str_t staging, sp_mem_slice_t slice); +SP_API sp_err_t sp_fs_write_atomic_slice_staged_at(sp_path_t path, sp_str_t staging, sp_mem_slice_t slice); SP_API sp_err_t sp_fs_write_atomic_cstr(sp_str_t path, const c8* str); +SP_API sp_err_t sp_fs_write_atomic_cstr_at(sp_path_t path, const c8* str); // ███████████ ███████████ ███████ █████████ ██████████ █████████ █████████ @@ -5055,7 +5085,9 @@ SP_IMP BOOL CALLBACK sp_tls_once_trampoline(PINIT_ONCE once, PVOID param, PVOI #endif // @fs +SP_IMP sp_path_t sp_path_at_root(sp_str_t sub); SP_IMP sp_fs_kind_t sp_fs_get_link_kind(sp_str_t path); +SP_IMP sp_fs_kind_t sp_fs_get_link_kind_at(sp_path_t path); // @tls SP_PRIVATE void sp_tls_new(sp_tls_key_t* key, sp_tls_deinit_fn_t fn); @@ -17314,9 +17346,9 @@ sp_err_t sp_io_file_reader_from_file(sp_io_file_reader_t* r, sp_sys_fd_t file, s return SP_OK; } -sp_err_t sp_io_file_reader_from_path(sp_io_file_reader_t* r, sp_str_t path) { +sp_err_t sp_io_file_reader_from_path_at(sp_io_file_reader_t* r, sp_path_t path) { sp_sys_fd_t fd = SP_SYS_INVALID_FD; - sp_err_t err = sp_sys_open_s(sp_sys_get_root(0), path, SP_SYS_OPEN_MODE_RO, 0, &fd); + sp_err_t err = sp_sys_open_s(path.dir, path.sub, SP_SYS_OPEN_MODE_RO, 0, &fd); if (err) { *r = sp_zero_s(sp_io_file_reader_t); return err; @@ -17324,6 +17356,10 @@ sp_err_t sp_io_file_reader_from_path(sp_io_file_reader_t* r, sp_str_t path) { return sp_io_file_reader_from_file(r, fd, SP_IO_CLOSE_MODE_AUTO); } +sp_err_t sp_io_file_reader_from_path(sp_io_file_reader_t* r, sp_str_t path) { + return sp_io_file_reader_from_path_at(r, sp_path_at_root(path)); +} + sp_err_t sp_io_file_reader_seek(sp_io_file_reader_t* r, s64 offset, sp_io_whence_t whence, s64* position) { sp_err_t err = SP_OK; s64 pos = 0; @@ -17983,10 +18019,10 @@ sp_err_t sp_io_file_writer_from_fd(sp_io_file_writer_t* w, sp_sys_fd_t fd, sp_io return SP_OK; } -sp_err_t sp_io_file_writer_from_path(sp_io_file_writer_t* w, sp_str_t path) { +sp_err_t sp_io_file_writer_from_path_at(sp_io_file_writer_t* w, sp_path_t path) { u32 flags = SP_SYS_OPEN_CREATE | SP_SYS_OPEN_TRUNCATE; sp_sys_fd_t fd = SP_SYS_INVALID_FD; - sp_err_t err = sp_sys_open_s(sp_sys_get_root(0), path, SP_SYS_OPEN_MODE_WO, flags, &fd); + sp_err_t err = sp_sys_open_s(path.dir, path.sub, SP_SYS_OPEN_MODE_WO, flags, &fd); if (err) { *w = sp_zero_s(sp_io_file_writer_t); return err; @@ -17997,6 +18033,10 @@ sp_err_t sp_io_file_writer_from_path(sp_io_file_writer_t* w, sp_str_t path) { return SP_OK; } +sp_err_t sp_io_file_writer_from_path(sp_io_file_writer_t* w, sp_str_t path) { + return sp_io_file_writer_from_path_at(w, sp_path_at_root(path)); +} + sp_err_t sp_io_writer_set_buffer(sp_io_writer_t* writer, u8* ptr, u64 size) { sp_assert(writer); sp_try(sp_io_flush(writer)); @@ -19840,58 +19880,102 @@ sp_str_t sp_fs_replace_ext(sp_mem_t mem, sp_str_t path, sp_str_t ext) { sp_str_join(mem, stripped, ext, sp_str_lit(".")); } -sp_fs_kind_t sp_fs_get_link_kind(sp_str_t path) { - if (sp_str_empty(path)) return SP_FS_KIND_NONE; +sp_path_t sp_path_at_root(sp_str_t sub) { + return (sp_path_t) { .dir = sp_sys_get_root(0), .sub = sub }; +} + +sp_fs_kind_t sp_fs_get_link_kind_at(sp_path_t path) { + if (sp_str_empty(path.sub)) return SP_FS_KIND_NONE; sp_sys_file_meta_t st = sp_zero; - s32 rc = sp_sys_get_link_metadata_s(sp_sys_get_root(0), path, &st); + s32 rc = sp_sys_get_link_metadata_s(path.dir, path.sub, &st); return rc == 0 ? st.kind : SP_FS_KIND_NONE; } -sp_fs_kind_t sp_fs_get_target_kind(sp_str_t path) { - if (sp_str_empty(path)) return SP_FS_KIND_NONE; +sp_fs_kind_t sp_fs_get_link_kind(sp_str_t path) { + return sp_fs_get_link_kind_at(sp_path_at_root(path)); +} + +sp_fs_kind_t sp_fs_get_target_kind_at(sp_path_t path) { + if (sp_str_empty(path.sub)) return SP_FS_KIND_NONE; sp_sys_file_meta_t st = sp_zero; - s32 rc = sp_sys_get_path_metadata_s(sp_sys_get_root(0), path, &st); + s32 rc = sp_sys_get_path_metadata_s(path.dir, path.sub, &st); return rc == 0 ? st.kind : SP_FS_KIND_NONE; } +sp_fs_kind_t sp_fs_get_target_kind(sp_str_t path) { + return sp_fs_get_target_kind_at(sp_path_at_root(path)); +} + +bool sp_fs_exists_at(sp_path_t path) { + return sp_fs_get_target_kind_at(path) != SP_FS_KIND_NONE; +} + bool sp_fs_exists(sp_str_t path) { - return sp_fs_get_target_kind(path) != SP_FS_KIND_NONE; + return sp_fs_exists_at(sp_path_at_root(path)); +} + +bool sp_fs_is_file_at(sp_path_t path) { + return sp_fs_get_link_kind_at(path) == SP_FS_KIND_FILE; } bool sp_fs_is_file(sp_str_t path) { - return sp_fs_get_link_kind(path) == SP_FS_KIND_FILE; + return sp_fs_is_file_at(sp_path_at_root(path)); +} + +bool sp_fs_is_symlink_at(sp_path_t path) { + return sp_fs_get_link_kind_at(path) == SP_FS_KIND_SYMLINK; } bool sp_fs_is_symlink(sp_str_t path) { - return sp_fs_get_link_kind(path) == SP_FS_KIND_SYMLINK; + return sp_fs_is_symlink_at(sp_path_at_root(path)); +} + +bool sp_fs_is_dir_at(sp_path_t path) { + return sp_fs_get_link_kind_at(path) == SP_FS_KIND_DIR; } bool sp_fs_is_dir(sp_str_t path) { - return sp_fs_get_link_kind(path) == SP_FS_KIND_DIR; + return sp_fs_is_dir_at(sp_path_at_root(path)); +} + +bool sp_fs_is_target_file_at(sp_path_t path) { + return sp_fs_get_target_kind_at(path) == SP_FS_KIND_FILE; } bool sp_fs_is_target_file(sp_str_t path) { - return sp_fs_get_target_kind(path) == SP_FS_KIND_FILE; + return sp_fs_is_target_file_at(sp_path_at_root(path)); +} + +bool sp_fs_is_target_dir_at(sp_path_t path) { + return sp_fs_get_target_kind_at(path) == SP_FS_KIND_DIR; } bool sp_fs_is_target_dir(sp_str_t path) { - return sp_fs_get_target_kind(path) == SP_FS_KIND_DIR; + return sp_fs_is_target_dir_at(sp_path_at_root(path)); +} + +sp_fs_kind_t sp_fs_get_kind_at(sp_path_t path) { + return sp_fs_get_link_kind_at(path); } sp_fs_kind_t sp_fs_get_kind(sp_str_t path) { - return sp_fs_get_link_kind(path); + return sp_fs_get_kind_at(sp_path_at_root(path)); } -sp_tm_epoch_t sp_fs_get_mod_time(sp_str_t path) { +sp_tm_epoch_t sp_fs_get_mod_time_at(sp_path_t path) { sp_tm_epoch_t result = sp_zero_s(sp_tm_epoch_t); sp_sys_file_meta_t st; - if (sp_sys_get_path_metadata_s(sp_sys_get_root(0), path, &st) == 0) { + if (sp_sys_get_path_metadata_s(path.dir, path.sub, &st) == 0) { result.s = (u64)st.mtime.tv_sec; result.ns = (u32)st.mtime.tv_nsec; } return result; } +sp_tm_epoch_t sp_fs_get_mod_time(sp_str_t path) { + return sp_fs_get_mod_time_at(sp_path_at_root(path)); +} + sp_str_t sp_fs_canonicalize_path(sp_mem_t mem, sp_str_t path) { if (sp_str_empty(path)) return sp_zero_s(sp_str_t); @@ -19964,7 +20048,7 @@ sp_str_t sp_fs_get_config_path(sp_mem_t mem) { return sp_fs_normalize_path(mem, sp_str(buf, len)); } -sp_err_t sp_io_read_file(sp_mem_t mem, sp_str_t path, sp_str_t* content) { +sp_err_t sp_io_read_file_at(sp_mem_t mem, sp_path_t path, sp_str_t* content) { sp_assert(content); sp_err_t err = SP_OK; sp_err_t read_err = SP_OK; @@ -19973,7 +20057,7 @@ sp_err_t sp_io_read_file(sp_mem_t mem, sp_str_t path, sp_str_t* content) { u64 bytes_read = 0; sp_io_file_reader_t reader = sp_zero; - sp_try(sp_io_file_reader_from_path(&reader, path)); + sp_try(sp_io_file_reader_from_path_at(&reader, path)); sp_try_goto(sp_io_file_reader_size(&reader, &size), err, cleanup); if (!size) goto cleanup; @@ -19997,6 +20081,10 @@ sp_err_t sp_io_read_file(sp_mem_t mem, sp_str_t path, sp_str_t* content) { return err; } +sp_err_t sp_io_read_file(sp_mem_t mem, sp_str_t path, sp_str_t* content) { + return sp_io_read_file_at(mem, sp_path_at_root(path), content); +} + SP_PRIVATE sp_err_t sp_fs_create_parent_at(sp_sys_fd_t dir, sp_str_t path); SP_PRIVATE sp_err_t sp_fs_mkdir_at(sp_sys_fd_t dir, sp_str_t path) { @@ -20008,46 +20096,71 @@ SP_PRIVATE sp_err_t sp_fs_mkdir_at(sp_sys_fd_t dir, sp_str_t path) { return st.kind == SP_FS_KIND_DIR ? SP_OK : err; } -SP_PRIVATE sp_err_t sp_fs_create_dir_at(sp_sys_fd_t dir, sp_str_t path) { - sp_err_t err = sp_fs_mkdir_at(dir, path); +sp_err_t sp_fs_create_dir_at(sp_path_t path) { + path.sub = sp_fs_trim_path(path.sub); + sp_err_t err = sp_fs_mkdir_at(path.dir, path.sub); if (err != SP_ERR_SYS_NOT_FOUND) return err; - sp_try(sp_fs_create_parent_at(dir, path)); - return sp_fs_mkdir_at(dir, path); + sp_try(sp_fs_create_parent_at(path.dir, path.sub)); + return sp_fs_mkdir_at(path.dir, path.sub); } sp_err_t sp_fs_create_dir(sp_str_t path) { - return sp_fs_create_dir_at(sp_sys_get_root(0), sp_fs_trim_path(path)); + return sp_fs_create_dir_at(sp_path_at_root(path)); } -sp_err_t sp_fs_create_file(sp_str_t path) { +sp_err_t sp_fs_create_file_at(sp_path_t path) { sp_sys_fd_t fd = SP_SYS_INVALID_FD; - sp_try(sp_sys_open_s(sp_sys_get_root(0), path, SP_SYS_OPEN_MODE_WO, SP_SYS_OPEN_CREATE | SP_SYS_OPEN_TRUNCATE, &fd)); + sp_try(sp_sys_open_s(path.dir, path.sub, SP_SYS_OPEN_MODE_WO, SP_SYS_OPEN_CREATE | SP_SYS_OPEN_TRUNCATE, &fd)); return sp_sys_close(fd); } -sp_err_t sp_fs_create_file_slice(sp_str_t path, sp_mem_slice_t slice) { +sp_err_t sp_fs_create_file(sp_str_t path) { + return sp_fs_create_file_at(sp_path_at_root(path)); +} + +sp_err_t sp_fs_create_file_slice_at(sp_path_t path, sp_mem_slice_t slice) { sp_io_file_writer_t io = sp_zero; - sp_try(sp_io_file_writer_from_path(&io, path)); + sp_try(sp_io_file_writer_from_path_at(&io, path)); sp_err_t written = sp_io_write_all(&io.base, slice.data, slice.len, SP_NULLPTR); sp_err_t closed = sp_io_file_writer_close(&io); return written ? written : closed; } +sp_err_t sp_fs_create_file_slice(sp_str_t path, sp_mem_slice_t slice) { + return sp_fs_create_file_slice_at(sp_path_at_root(path), slice); +} + +sp_err_t sp_fs_create_file_str_at(sp_path_t path, sp_str_t str) { + return sp_fs_create_file_slice_at(path, sp_mem_slice((u8*)str.data, str.len)); +} + sp_err_t sp_fs_create_file_str(sp_str_t path, sp_str_t str) { - return sp_fs_create_file_slice(path, sp_mem_slice((u8*)str.data, str.len)); + return sp_fs_create_file_str_at(sp_path_at_root(path), str); +} + +sp_err_t sp_fs_create_file_cstr_at(sp_path_t path, const c8* str) { + return sp_fs_create_file_str_at(path, sp_str_view(str)); } sp_err_t sp_fs_create_file_cstr(sp_str_t path, const c8* str) { - return sp_fs_create_file_str(path, sp_str_view(str)); + return sp_fs_create_file_cstr_at(sp_path_at_root(path), str); +} + +sp_err_t sp_fs_create_hard_link_at(sp_path_t target, sp_path_t link_path) { + return sp_sys_link_s(target.dir, target.sub, link_path.dir, link_path.sub); } sp_err_t sp_fs_create_hard_link(sp_str_t target, sp_str_t link_path) { - return sp_sys_link_s(sp_sys_get_root(0), target, sp_sys_get_root(0), link_path); + return sp_fs_create_hard_link_at(sp_path_at_root(target), sp_path_at_root(link_path)); +} + +sp_err_t sp_fs_create_sym_link_at(sp_str_t target, sp_path_t link_path) { + return sp_sys_symlink_s(target, link_path.dir, link_path.sub); } sp_err_t sp_fs_create_sym_link(sp_str_t target, sp_str_t link_path) { - return sp_sys_symlink_s(target, sp_sys_get_root(0), link_path); + return sp_fs_create_sym_link_at(target, sp_path_at_root(link_path)); } sp_err_t sp_fs_link(sp_str_t from, sp_str_t to, sp_fs_link_kind_t kind) { @@ -20058,8 +20171,12 @@ sp_err_t sp_fs_link(sp_str_t from, sp_str_t to, sp_fs_link_kind_t kind) { SP_UNREACHABLE_RETURN(SP_OK); } +sp_err_t sp_fs_remove_file_at(sp_path_t path) { + return sp_sys_unlink_s(path.dir, path.sub); +} + sp_err_t sp_fs_remove_file(sp_str_t path) { - return sp_sys_unlink_s(sp_sys_get_root(0), path); + return sp_fs_remove_file_at(sp_path_at_root(path)); } sp_err_t sp_fs_dir_open(sp_fs_dir_t* it, sp_sys_fd_t fd, sp_str_t path, sp_mem_slice_t buf) { @@ -20281,7 +20398,7 @@ SP_PRIVATE sp_str_t sp_fs_temp_name(sp_str_t dir, sp_str_t name, c8 buf [SP_PATH SP_PRIVATE sp_err_t sp_fs_create_parent_at(sp_sys_fd_t dir, sp_str_t path) { sp_str_t parent = sp_fs_parent_path(path); if (sp_str_empty(parent)) return SP_OK; - sp_err_t err = sp_fs_create_dir_at(dir, parent); + sp_err_t err = sp_fs_create_dir_at((sp_path_t) { .dir = dir, .sub = parent }); return err == SP_ERR_SYS_EXISTS ? SP_ERR_SYS_NOT_DIR : err; } @@ -20311,17 +20428,21 @@ SP_PRIVATE sp_err_t sp_fs_atomic_create(sp_fs_atomic_t* af, sp_sys_fd_t dir, sp_ return err; } -sp_err_t sp_fs_atomic_open_at(sp_fs_atomic_t* af, sp_sys_fd_t dir, sp_str_t path) { - return sp_fs_atomic_create(af, dir, path, sp_fs_parent_path(path)); +sp_err_t sp_fs_atomic_open_at(sp_fs_atomic_t* af, sp_path_t path) { + return sp_fs_atomic_create(af, path.dir, path.sub, sp_fs_parent_path(path.sub)); } sp_err_t sp_fs_atomic_open(sp_fs_atomic_t* af, sp_str_t path) { - return sp_fs_atomic_create(af, sp_sys_get_root(0), path, sp_fs_parent_path(path)); + return sp_fs_atomic_open_at(af, sp_path_at_root(path)); } -sp_err_t sp_fs_atomic_open_staged(sp_fs_atomic_t* af, sp_str_t path, sp_str_t staging) { +sp_err_t sp_fs_atomic_open_staged_at(sp_fs_atomic_t* af, sp_path_t path, sp_str_t staging) { if (sp_str_empty(staging)) return SP_ERR_SYS_BUG; - return sp_fs_atomic_create(af, sp_sys_get_root(0), path, staging); + return sp_fs_atomic_create(af, path.dir, path.sub, staging); +} + +sp_err_t sp_fs_atomic_open_staged(sp_fs_atomic_t* af, sp_str_t path, sp_str_t staging) { + return sp_fs_atomic_open_staged_at(af, sp_path_at_root(path), staging); } sp_io_writer_t* sp_fs_atomic_writer(sp_fs_atomic_t* af) { @@ -20378,26 +20499,42 @@ SP_PRIVATE sp_err_t sp_fs_atomic_write(sp_fs_atomic_t* af, const void* data, u64 return sp_fs_atomic_commit(af, SP_FS_ATOMIC_REPLACE); } -sp_err_t sp_fs_write_atomic_slice_staged(sp_str_t path, sp_str_t staging, sp_mem_slice_t slice) { +sp_err_t sp_fs_write_atomic_slice_staged_at(sp_path_t path, sp_str_t staging, sp_mem_slice_t slice) { sp_fs_atomic_t af = sp_zero; - sp_try(sp_fs_atomic_open_staged(&af, path, staging)); + sp_try(sp_fs_atomic_open_staged_at(&af, path, staging)); return sp_fs_atomic_write(&af, slice.data, slice.len); } -sp_err_t sp_fs_write_atomic_slice(sp_str_t path, sp_mem_slice_t slice) { +sp_err_t sp_fs_write_atomic_slice_staged(sp_str_t path, sp_str_t staging, sp_mem_slice_t slice) { + return sp_fs_write_atomic_slice_staged_at(sp_path_at_root(path), staging, slice); +} + +sp_err_t sp_fs_write_atomic_slice_at(sp_path_t path, sp_mem_slice_t slice) { sp_fs_atomic_t af = sp_zero; - sp_try(sp_fs_atomic_open(&af, path)); + sp_try(sp_fs_atomic_open_at(&af, path)); return sp_fs_atomic_write(&af, slice.data, slice.len); } -sp_err_t sp_fs_write_atomic(sp_str_t path, sp_str_t str) { +sp_err_t sp_fs_write_atomic_slice(sp_str_t path, sp_mem_slice_t slice) { + return sp_fs_write_atomic_slice_at(sp_path_at_root(path), slice); +} + +sp_err_t sp_fs_write_atomic_at(sp_path_t path, sp_str_t str) { sp_fs_atomic_t af = sp_zero; - sp_try(sp_fs_atomic_open(&af, path)); + sp_try(sp_fs_atomic_open_at(&af, path)); return sp_fs_atomic_write(&af, str.data, str.len); } +sp_err_t sp_fs_write_atomic(sp_str_t path, sp_str_t str) { + return sp_fs_write_atomic_at(sp_path_at_root(path), str); +} + +sp_err_t sp_fs_write_atomic_cstr_at(sp_path_t path, const c8* str) { + return sp_fs_write_atomic_at(path, sp_str_view(str)); +} + sp_err_t sp_fs_write_atomic_cstr(sp_str_t path, const c8* str) { - return sp_fs_write_atomic(path, sp_str_view(str)); + return sp_fs_write_atomic_cstr_at(sp_path_at_root(path), str); } SP_PRIVATE sp_err_t sp_fs_copy_bytes(sp_str_t from, sp_str_t to, sp_fs_atomic_mode_t mode, sp_sys_file_perms_t perms) { diff --git a/test/fs/atomic.c b/test/fs/atomic.c index 91ff9878..edbf7443 100644 --- a/test/fs/atomic.c +++ b/test/fs/atomic.c @@ -225,7 +225,7 @@ sp_test_each(fs, atomic_open_at, test_t, open_at_tests) { sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, &dir)); sp_fs_atomic_t af = sp_zero; - sp_err_t err = drive(&af, sp_fs_atomic_open_at(&af, dir, sp_str_view(it->path)), it); + sp_err_t err = drive(&af, sp_fs_atomic_open_at(&af, (sp_path_t) { .dir = dir, .sub = sp_str_view(it->path) }), it); sp_sys_close(dir); sp_expect_err_eq(t, err, it->expect.err); @@ -235,15 +235,17 @@ sp_test_each(fs, atomic_open_at, test_t, open_at_tests) { } sp_test_each(fs, atomic_staged, test_t, staged_tests) { - sp_mem_t mem = sp_test_arena(t); sp_str_t sandbox = sp_test_dir(t); fs_apply_setup(t, sandbox, it->setup); - sp_str_t path = sp_fs_join_path(mem, sandbox, sp_str_view(it->path)); - sp_str_t staging = sp_fs_join_path(mem, sandbox, sp_str_lit("S")); + sp_sys_fd_t dir = SP_SYS_INVALID_FD; + sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, &dir)); + + sp_path_t path = { .dir = dir, .sub = sp_str_view(it->path) }; sp_fs_atomic_t af = sp_zero; - sp_err_t err = drive(&af, sp_fs_atomic_open_staged(&af, path, staging), it); + sp_err_t err = drive(&af, sp_fs_atomic_open_staged_at(&af, path, sp_str_lit("S")), it); + sp_sys_close(dir); sp_expect_err_eq(t, err, it->expect.err); fs_expect_paths(t, sandbox, it->expect.paths); @@ -252,9 +254,13 @@ sp_test_each(fs, atomic_staged, test_t, staged_tests) { } sp_test(fs, atomic_staged_empty_staging_is_bug) { - sp_str_t path = sp_fs_join_path(sp_test_arena(t), sp_test_dir(t), sp_str_lit("A")); + sp_sys_fd_t dir = SP_SYS_INVALID_FD; + sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sp_test_dir(t), &dir)); + + sp_path_t path = { .dir = dir, .sub = sp_str_lit("A") }; sp_fs_atomic_t af = sp_zero; - sp_expect_err_eq(t, sp_fs_atomic_open_staged(&af, path, sp_str_lit("")), SP_ERR_SYS_BUG); + sp_expect_err_eq(t, sp_fs_atomic_open_staged_at(&af, path, sp_str_lit("")), SP_ERR_SYS_BUG); + sp_sys_close(dir); return SP_OK; } diff --git a/test/fs/create_dir.c b/test/fs/create_dir.c index 0a90e7e8..7a855137 100644 --- a/test/fs/create_dir.c +++ b/test/fs/create_dir.c @@ -136,8 +136,12 @@ sp_test_each(fs, create_dir, test_t, tests) { sp_str_t sandbox = sp_test_dir(t); fs_apply_setup(t, sandbox, it->setup); - sp_str_t target = sp_fs_join_path(sp_test_arena(t), sandbox, sp_str_view(it->target)); - sp_expect_err_eq(t, sp_fs_create_dir(target), it->expect.err); + sp_sys_fd_t dir = SP_SYS_INVALID_FD; + sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, &dir)); + + sp_path_t target = { dir, sp_cstr_as_str(it->target) }; + sp_expect_err_eq(t, sp_fs_create_dir_at(target), it->expect.err); + sp_sys_close(dir); fs_expect_paths(t, sandbox, it->expect.paths); return SP_OK; diff --git a/test/fs/create_file.c b/test/fs/create_file.c index 01481848..0c89752c 100644 --- a/test/fs/create_file.c +++ b/test/fs/create_file.c @@ -74,26 +74,30 @@ sp_test_each(fs, create_file, test_t, tests) { sp_str_t sandbox = sp_test_dir(t); fs_apply_setup(t, sandbox, it->setup); - sp_str_t path = sp_fs_join_path(sp_test_arena(t), sandbox, sp_str_view(it->path)); + sp_sys_fd_t dir = SP_SYS_INVALID_FD; + sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, &dir)); + + sp_path_t path = { .dir = dir, .sub = sp_str_view(it->path) }; sp_err_t result = SP_OK; switch (it->variant) { case VARIANT_EMPTY: { - result = sp_fs_create_file(path); + result = sp_fs_create_file_at(path); break; } case VARIANT_SLICE: { - result = sp_fs_create_file_slice(path, sp_mem_slice((u8*)it->content, sp_cstr_len(it->content))); + result = sp_fs_create_file_slice_at(path, sp_mem_slice((u8*)it->content, sp_cstr_len(it->content))); break; } case VARIANT_STR: { - result = sp_fs_create_file_str(path, sp_str_view(it->content)); + result = sp_fs_create_file_str_at(path, sp_str_view(it->content)); break; } case VARIANT_CSTR: { - result = sp_fs_create_file_cstr(path, it->content); + result = sp_fs_create_file_cstr_at(path, it->content); break; } } + sp_sys_close(dir); sp_expect_ok(t, result); fs_expect_paths(t, sandbox, it->expect); diff --git a/test/fs/links.c b/test/fs/links.c index 792128cb..d8ab2354 100644 --- a/test/fs/links.c +++ b/test/fs/links.c @@ -124,22 +124,26 @@ sp_test_each(fs, links, test_t, tests) { sp_str_t sandbox = sp_test_dir(t); fs_apply_setup(t, sandbox, it->setup); - sp_str_t target = sp_fs_join_path(mem, sandbox, sp_str_view(it->target)); - sp_str_t link = sp_fs_join_path(mem, sandbox, sp_str_view(it->link)); + sp_sys_fd_t dir = SP_SYS_INVALID_FD; + sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, &dir)); + + sp_path_t target = { .dir = dir, .sub = sp_str_view(it->target) }; + sp_path_t link = { .dir = dir, .sub = sp_str_view(it->link) }; sp_err_t result = it->symlink - ? sp_fs_create_sym_link(target, link) - : sp_fs_create_hard_link(target, link); + ? sp_fs_create_sym_link_at(sp_fs_join_path(mem, sandbox, target.sub), link) + : sp_fs_create_hard_link_at(target, link); sp_expect_err_eq(t, result, it->expect.err); // a hard link shares content with its target: rewrite the target through // one name, then the expected paths observe the update through the other if (it->rewrite) { sp_io_file_writer_t writer = sp_zero; - sp_io_file_writer_from_path(&writer, target); + sp_io_file_writer_from_path_at(&writer, target); sp_io_write_str(&writer.base, sp_str_view(it->rewrite), SP_NULLPTR); sp_io_file_writer_close(&writer); } + sp_sys_close(dir); fs_expect_paths(t, sandbox, it->expect.paths); return SP_OK; diff --git a/test/fs/mod_time.c b/test/fs/mod_time.c index d7f5200a..5f4ede8e 100644 --- a/test/fs/mod_time.c +++ b/test/fs/mod_time.c @@ -1,29 +1,37 @@ #include "fs.h" sp_test(fs, mod_time_nonzero) { - sp_str_t file = fs_path(t, sp_str_lit("A")); - sp_str_t dir = fs_path(t, sp_str_lit("B")); - sp_fs_create_file(file); - sp_fs_create_dir(dir); + sp_sys_fd_t sandbox = SP_SYS_INVALID_FD; + sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sp_test_dir(t), &sandbox)); - sp_expect(t, sp_fs_get_mod_time(file).s > 0); - sp_expect(t, sp_fs_get_mod_time(dir).s > 0); + sp_path_t file = { .dir = sandbox, .sub = sp_str_lit("A") }; + sp_path_t dir = { .dir = sandbox, .sub = sp_str_lit("B") }; + sp_fs_create_file_at(file); + sp_fs_create_dir_at(dir); + + sp_expect(t, sp_fs_get_mod_time_at(file).s > 0); + sp_expect(t, sp_fs_get_mod_time_at(dir).s > 0); + sp_sys_close(sandbox); return SP_OK; } sp_test(fs, mod_time_updates_after_write) { - sp_str_t file = fs_path(t, sp_str_lit("A")); - sp_fs_create_file_str(file, sp_str_lit("A")); + sp_sys_fd_t sandbox = SP_SYS_INVALID_FD; + sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sp_test_dir(t), &sandbox)); + + sp_path_t file = { sandbox, sp_str_lit("A") }; + sp_fs_create_file_str_at(file, sp_str_lit("A")); - sp_tm_epoch_t before = sp_fs_get_mod_time(file); + sp_tm_epoch_t before = sp_fs_get_mod_time_at(file); sp_os_sleep_ms(100); sp_io_file_writer_t writer = sp_zero; - sp_io_file_writer_from_path(&writer, file); + sp_io_file_writer_from_path_at(&writer, file); sp_io_write_str(&writer.base, sp_str_lit("B"), SP_NULLPTR); sp_io_file_writer_close(&writer); - sp_tm_epoch_t after = sp_fs_get_mod_time(file); + sp_tm_epoch_t after = sp_fs_get_mod_time_at(file); + sp_sys_close(sandbox); sp_must(t, after.s > before.s || (after.s == before.s && after.ns > before.ns)); return SP_OK; } diff --git a/test/fs/predicates.c b/test/fs/predicates.c index f95b8581..a5ec4160 100644 --- a/test/fs/predicates.c +++ b/test/fs/predicates.c @@ -91,22 +91,26 @@ static const test_t tests [] = { sp_test_each(fs, predicates, test_t, tests) { skip_if_symlinks_needed(t, it->setup); - sp_mem_t mem = sp_test_arena(t); sp_str_t sandbox = sp_test_dir(t); fs_apply_setup(t, sandbox, it->setup); + sp_sys_fd_t dir = SP_SYS_INVALID_FD; + sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, &dir)); + sp_carr_for(it->probes, i) { const probe_t* probe = &it->probes[i]; if (!probe->path) break; - sp_str_t path = sp_fs_join_path(mem, sandbox, sp_str_view(probe->path)); + sp_path_t path = { dir, sp_cstr_as_str(probe->path) }; - fs_expect_bool(t, path, "exists", sp_fs_exists(path), probe->exists); - fs_expect_bool(t, path, "is_regular_file", sp_fs_is_file(path), probe->file); - fs_expect_bool(t, path, "is_dir", sp_fs_is_dir(path), probe->dir); - fs_expect_bool(t, path, "is_symlink", sp_fs_is_symlink(path), probe->symlink); - fs_expect_bool(t, path, "is_target_regular_file", sp_fs_is_target_file(path), probe->target_file); - fs_expect_bool(t, path, "is_target_dir", sp_fs_is_target_dir(path), probe->target_dir); - fs_expect_kind(t, path, sp_fs_get_kind(path), probe->kind); + fs_expect_bool(t, path.sub, "exists", sp_fs_exists_at(path), probe->exists); + fs_expect_bool(t, path.sub, "is_regular_file", sp_fs_is_file_at(path), probe->file); + fs_expect_bool(t, path.sub, "is_dir", sp_fs_is_dir_at(path), probe->dir); + fs_expect_bool(t, path.sub, "is_symlink", sp_fs_is_symlink_at(path), probe->symlink); + fs_expect_bool(t, path.sub, "is_target_regular_file", sp_fs_is_target_file_at(path), probe->target_file); + fs_expect_bool(t, path.sub, "is_target_dir", sp_fs_is_target_dir_at(path), probe->target_dir); + fs_expect_kind(t, path.sub, sp_fs_get_kind_at(path), probe->kind); } + + sp_sys_close(dir); return SP_OK; } diff --git a/test/fs/remove.c b/test/fs/remove.c index 9dc9e115..d25d6c1f 100644 --- a/test/fs/remove.c +++ b/test/fs/remove.c @@ -151,10 +151,14 @@ sp_test_each(fs, remove, test_t, tests) { sp_str_t sandbox = sp_test_dir(t); fs_apply_setup(t, sandbox, it->setup); - sp_str_t path = sp_fs_join_path(sp_test_arena(t), sandbox, sp_str_view(it->path)); + sp_sys_fd_t dir = SP_SYS_INVALID_FD; + sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, &dir)); + + sp_path_t path = { .dir = dir, .sub = sp_str_view(it->path) }; sp_err_t result = it->dir - ? sp_fs_remove_dir(path) - : sp_fs_remove_file(path); + ? sp_fs_remove_dir(sp_fs_join_path(sp_test_arena(t), sandbox, path.sub)) + : sp_fs_remove_file_at(path); + sp_sys_close(dir); sp_expect_err_eq(t, result, it->err); fs_expect_paths(t, sandbox, it->expect); diff --git a/test/io/discard.c b/test/io/discard.c index 656fc0cd..805aee60 100644 --- a/test/io/discard.c +++ b/test/io/discard.c @@ -83,13 +83,13 @@ UTEST_F(io_discard, eof_with_final_bytes_is_ok) { UTEST_F(io, discard_file_skips_by_position) { { sp_io_file_writer_t w = sp_zero; - ASSERT_EQ(sp_io_file_writer_from_path(&w, ut.file_path), SP_OK); + ASSERT_EQ(sp_io_file_writer_from_path_at(&w, ut.file), SP_OK); sp_io_write(&w.base, "hello world", 11, SP_NULLPTR); sp_io_file_writer_close(&w); } sp_io_file_reader_t r = sp_zero; - ASSERT_EQ(sp_io_file_reader_from_path(&r, ut.file_path), SP_OK); + ASSERT_EQ(sp_io_file_reader_from_path_at(&r, ut.file), SP_OK); u64 discarded = 0; EXPECT_EQ(sp_io_discard(&r.base, 6, &discarded), SP_OK); diff --git a/test/io/file.c b/test/io/file.c index 046df888..36c6c240 100644 --- a/test/io/file.c +++ b/test/io/file.c @@ -9,16 +9,16 @@ typedef struct { io_step_t steps [IO_MAX_STEPS]; } io_file_reader_test_t; -void run_io_file_reader_test(int* utest_result, sp_str_t path, io_file_reader_test_t t) { +void run_io_file_reader_test(int* utest_result, sp_path_t path, io_file_reader_test_t t) { { sp_io_file_writer_t w = sp_zero; - sp_io_file_writer_from_path(&w, path); + sp_io_file_writer_from_path_at(&w, path); if (t.content) sp_io_write(&w.base, t.content, sp_cstr_len(t.content), SP_NULLPTR); sp_io_file_writer_close(&w); } sp_io_file_reader_t r = sp_zero; - sp_io_file_reader_from_path(&r, path); + sp_io_file_reader_from_path_at(&r, path); u8 wrapper_buf [64] = sp_zero; if (t.buffer) sp_io_reader_set_buffer(&r.base, wrapper_buf, t.buffer); @@ -62,7 +62,7 @@ void run_io_file_reader_test(int* utest_result, sp_str_t path, io_file_reader_te } UTEST_F(io, file_reader_read_full) { - run_io_file_reader_test(utest_result, ut.file_path, (io_file_reader_test_t){ + run_io_file_reader_test(utest_result, ut.file, (io_file_reader_test_t){ .content = "0123456789ABCDEF", .steps = { { .kind = IO_STEP_READ, .read = { 16, SP_OK, "0123456789ABCDEF" } }, @@ -71,7 +71,7 @@ UTEST_F(io, file_reader_read_full) { } UTEST_F(io, file_reader_eof_after_drain) { - run_io_file_reader_test(utest_result, ut.file_path, (io_file_reader_test_t){ + run_io_file_reader_test(utest_result, ut.file, (io_file_reader_test_t){ .content = "0123456789ABCDEF", .steps = { { .kind = IO_STEP_READ, .read = { 16, SP_OK, "0123456789ABCDEF" } }, @@ -81,7 +81,7 @@ UTEST_F(io, file_reader_eof_after_drain) { } UTEST_F(io, file_reader_eof_short) { - run_io_file_reader_test(utest_result, ut.file_path, (io_file_reader_test_t){ + run_io_file_reader_test(utest_result, ut.file, (io_file_reader_test_t){ .content = "short", .steps = { { .kind = IO_STEP_READ, .read = { 32, SP_OK, "short" } }, @@ -91,7 +91,7 @@ UTEST_F(io, file_reader_eof_short) { } UTEST_F(io, file_reader_eof_empty) { - run_io_file_reader_test(utest_result, ut.file_path, (io_file_reader_test_t){ + run_io_file_reader_test(utest_result, ut.file, (io_file_reader_test_t){ .content = "", .steps = { { .kind = IO_STEP_READ, .read = { 16, SP_ERR_IO_EOF } }, @@ -100,7 +100,7 @@ UTEST_F(io, file_reader_eof_empty) { } UTEST_F(io, file_reader_seek) { - run_io_file_reader_test(utest_result, ut.file_path, (io_file_reader_test_t){ + run_io_file_reader_test(utest_result, ut.file, (io_file_reader_test_t){ .content = "0123456789", .steps = { { .kind = IO_STEP_SEEK, .seek = { 5, SP_IO_SEEK_SET, SP_OK, 5 } }, @@ -110,7 +110,7 @@ UTEST_F(io, file_reader_seek) { } UTEST_F(io, file_reader_buffered_read) { - run_io_file_reader_test(utest_result, ut.file_path, (io_file_reader_test_t){ + run_io_file_reader_test(utest_result, ut.file, (io_file_reader_test_t){ .content = "0123456789ABCDEF", .buffer = 8, .steps = { @@ -120,7 +120,7 @@ UTEST_F(io, file_reader_buffered_read) { } UTEST_F(io, file_reader_buffered_seek_discards_buffer) { - run_io_file_reader_test(utest_result, ut.file_path, (io_file_reader_test_t){ + run_io_file_reader_test(utest_result, ut.file, (io_file_reader_test_t){ .content = "0123456789ABCDEF", .buffer = 8, .steps = { @@ -132,16 +132,16 @@ UTEST_F(io, file_reader_buffered_seek_discards_buffer) { } UTEST_F(io, file_reader_nonexistent) { - sp_str_t path = sp_test_file_path(&ut.file_manager, sp_str_lit("nonexistent.file")); + sp_path_t path = { .dir = ut.file.dir, .sub = sp_str_lit("nonexistent.file") }; sp_io_file_reader_t r = sp_zero; - EXPECT_EQ(sp_io_file_reader_from_path(&r, path), SP_ERR_SYS_NOT_FOUND); + EXPECT_EQ(sp_io_file_reader_from_path_at(&r, path), SP_ERR_SYS_NOT_FOUND); sp_io_file_reader_close(&r); } UTEST_F(io, file_writer_nonexistent_dir) { - sp_str_t path = sp_test_file_path(&ut.file_manager, sp_str_lit("missing-dir/nested.file")); + sp_path_t path = { .dir = ut.file.dir, .sub = sp_str_lit("missing-dir/nested.file") }; sp_io_file_writer_t w = sp_zero; - EXPECT_EQ(sp_io_file_writer_from_path(&w, path), SP_ERR_SYS_NOT_FOUND); + EXPECT_EQ(sp_io_file_writer_from_path_at(&w, path), SP_ERR_SYS_NOT_FOUND); } @@ -157,16 +157,16 @@ typedef struct { } expect; } io_file_writer_test_t; -void run_io_file_writer_test(int* utest_result, sp_mem_t mem, sp_str_t path, io_file_writer_test_t t) { +void run_io_file_writer_test(int* utest_result, sp_mem_t mem, sp_path_t path, io_file_writer_test_t t) { if (t.pre_content) { sp_io_file_writer_t w = sp_zero; - sp_io_file_writer_from_path(&w, path); + sp_io_file_writer_from_path_at(&w, path); sp_io_write(&w.base, t.pre_content, sp_cstr_len(t.pre_content), SP_NULLPTR); sp_io_file_writer_close(&w); } sp_io_file_writer_t w = sp_zero; - sp_io_file_writer_from_path(&w, path); + sp_io_file_writer_from_path_at(&w, path); u8 wrapper_buf [64] = sp_zero; if (t.buffer) sp_io_writer_set_buffer(&w.base, wrapper_buf, t.buffer); @@ -215,7 +215,7 @@ void run_io_file_writer_test(int* utest_result, sp_mem_t mem, sp_str_t path, io_ if (t.expect.content) { sp_str_t loaded = sp_zero; - sp_io_read_file(mem, path, &loaded); + sp_io_read_file_at(mem, path, &loaded); u64 n = sp_cstr_len(t.expect.content); EXPECT_EQ(loaded.len, n); sp_for(it, n) { @@ -225,7 +225,7 @@ void run_io_file_writer_test(int* utest_result, sp_mem_t mem, sp_str_t path, io_ } UTEST_F(io, file_writer_write) { - run_io_file_writer_test(utest_result, ut.mem, ut.file_path, (io_file_writer_test_t){ + run_io_file_writer_test(utest_result, ut.mem, ut.file, (io_file_writer_test_t){ .steps = { { .kind = IO_STEP_WRITE, .write = { "test data", SP_OK, 9 } }, }, @@ -234,7 +234,7 @@ UTEST_F(io, file_writer_write) { } UTEST_F(io, file_writer_overwrite) { - run_io_file_writer_test(utest_result, ut.mem, ut.file_path, (io_file_writer_test_t){ + run_io_file_writer_test(utest_result, ut.mem, ut.file, (io_file_writer_test_t){ .pre_content = "XXXXXXXX", .steps = { { .kind = IO_STEP_WRITE, .write = { "1234", SP_OK, 4 } }, @@ -244,7 +244,7 @@ UTEST_F(io, file_writer_overwrite) { } UTEST_F(io, file_writer_size) { - run_io_file_writer_test(utest_result, ut.mem, ut.file_path, (io_file_writer_test_t){ + run_io_file_writer_test(utest_result, ut.mem, ut.file, (io_file_writer_test_t){ .steps = { { .kind = IO_STEP_WRITE, .write = { "0123456789ABCDEF", SP_OK, 16 } }, { .kind = IO_STEP_SIZE, .size = { SP_OK, 16 } }, @@ -254,7 +254,7 @@ UTEST_F(io, file_writer_size) { } UTEST_F(io, file_writer_buffered_implicit_flush) { - run_io_file_writer_test(utest_result, ut.mem, ut.file_path, (io_file_writer_test_t){ + run_io_file_writer_test(utest_result, ut.mem, ut.file, (io_file_writer_test_t){ .buffer = 64, .steps = { { .kind = IO_STEP_WRITE, .write = { "hello", SP_OK, 5 } }, @@ -264,7 +264,7 @@ UTEST_F(io, file_writer_buffered_implicit_flush) { } UTEST_F(io, file_writer_buffered_larger_than_buffer) { - run_io_file_writer_test(utest_result, ut.mem, ut.file_path, (io_file_writer_test_t){ + run_io_file_writer_test(utest_result, ut.mem, ut.file, (io_file_writer_test_t){ .buffer = 4, .steps = { { .kind = IO_STEP_WRITE, .write = { "0123456789ABCDEF", SP_OK, 16 } }, @@ -283,14 +283,14 @@ UTEST_F(io, file_writer_buffered_larger_than_buffer) { // nul). Kept imperative. UTEST_F(io, file_writer_pad) { sp_io_file_writer_t w = sp_zero; - sp_io_file_writer_from_path(&w, ut.file_path); + sp_io_file_writer_from_path_at(&w, ut.file); sp_io_write(&w.base, "AA", 2, SP_NULLPTR); sp_io_pad(&w.base, 3, SP_NULLPTR); sp_io_write(&w.base, "BB", 2, SP_NULLPTR); sp_io_file_writer_close(&w); sp_io_file_reader_t r = sp_zero; - sp_io_file_reader_from_path(&r, ut.file_path); + sp_io_file_reader_from_path_at(&r, ut.file); u8 result [7] = sp_zero; u64 bytes = 0; EXPECT_EQ(sp_io_read(&r.base, result, 7, &bytes), SP_OK); @@ -309,12 +309,12 @@ UTEST_F(io, file_writer_pad) { // This is what the writer-side fast path keys off of. UTEST_F(io, file_reader_as_file) { sp_io_file_writer_t fw = sp_zero; - sp_io_file_writer_from_path(&fw, ut.file_path); + sp_io_file_writer_from_path_at(&fw, ut.file); sp_io_write(&fw.base, "x", 1, SP_NULLPTR); sp_io_file_writer_close(&fw); sp_io_file_reader_t r = sp_zero; - sp_io_file_reader_from_path(&r, ut.file_path); + sp_io_file_reader_from_path_at(&r, ut.file); EXPECT_TRUE(r.base.as_file != SP_NULLPTR); sp_sys_fd_t fd = SP_SYS_INVALID_FD; @@ -331,12 +331,12 @@ UTEST_F(io, file_reader_as_file) { // fd of unknown kind never claims it. UTEST_F(io, stream_reader_as_file) { sp_io_file_writer_t fw = sp_zero; - sp_io_file_writer_from_path(&fw, ut.file_path); + sp_io_file_writer_from_path_at(&fw, ut.file); sp_io_write(&fw.base, "x", 1, SP_NULLPTR); sp_io_file_writer_close(&fw); sp_sys_fd_t file = SP_SYS_INVALID_FD; - ASSERT_EQ(sp_sys_open_s(sp_sys_get_root(0), ut.file_path, SP_SYS_OPEN_MODE_RO, 0, &file), SP_OK); + ASSERT_EQ(sp_sys_open_s(ut.file.dir, ut.file.sub, SP_SYS_OPEN_MODE_RO, 0, &file), SP_OK); sp_io_stream_reader_t r = sp_zero; sp_io_stream_reader_from_file(&r, file, SP_IO_CLOSE_MODE_AUTO); @@ -366,16 +366,16 @@ UTEST_F(io, file_to_file_copy) { sp_for(i, sizeof(source)) source[i] = (u8)((i * 1103515245u + 12345u) >> 8); sp_io_file_writer_t sw = sp_zero; - sp_io_file_writer_from_path(&sw, ut.file_path); + sp_io_file_writer_from_path_at(&sw, ut.file); EXPECT_EQ(sp_io_write(&sw.base, source, sizeof(source), SP_NULLPTR), SP_OK); sp_io_file_writer_close(&sw); - sp_str_t dst_path = sp_test_file_path(&ut.file_manager, sp_str_lit("file_to_file_copy.dst")); + sp_path_t dst_path = { .dir = ut.file.dir, .sub = sp_str_lit("file_to_file_copy.dst") }; sp_io_file_reader_t r = sp_zero; - sp_io_file_reader_from_path(&r, ut.file_path); + sp_io_file_reader_from_path_at(&r, ut.file); sp_io_file_writer_t w = sp_zero; - sp_io_file_writer_from_path(&w, dst_path); + sp_io_file_writer_from_path_at(&w, dst_path); u64 copied = 0; EXPECT_EQ(sp_io_copy(&w.base, &r.base, &copied), SP_OK); @@ -385,7 +385,7 @@ UTEST_F(io, file_to_file_copy) { sp_io_file_writer_close(&w); sp_str_t loaded = sp_zero; - sp_io_read_file(ut.mem, dst_path, &loaded); + sp_io_read_file_at(ut.mem, dst_path, &loaded); EXPECT_EQ(loaded.len, sizeof(source)); sp_for(i, sizeof(source)) EXPECT_EQ((u8)loaded.data[i], source[i]); } @@ -398,18 +398,18 @@ UTEST_F(io, stream_file_to_file_copy) { sp_for(i, sizeof(source)) source[i] = (u8)((i * 1103515245u + 12345u) >> 8); sp_io_file_writer_t sw = sp_zero; - sp_io_file_writer_from_path(&sw, ut.file_path); + sp_io_file_writer_from_path_at(&sw, ut.file); EXPECT_EQ(sp_io_write(&sw.base, source, sizeof(source), SP_NULLPTR), SP_OK); sp_io_file_writer_close(&sw); - sp_str_t dst_path = sp_test_file_path(&ut.file_manager, sp_str_lit("stream_file_to_file_copy.dst")); + sp_path_t dst_path = { .dir = ut.file.dir, .sub = sp_str_lit("stream_file_to_file_copy.dst") }; sp_sys_fd_t file = SP_SYS_INVALID_FD; - ASSERT_EQ(sp_sys_open_s(sp_sys_get_root(0), ut.file_path, SP_SYS_OPEN_MODE_RO, 0, &file), SP_OK); + ASSERT_EQ(sp_sys_open_s(ut.file.dir, ut.file.sub, SP_SYS_OPEN_MODE_RO, 0, &file), SP_OK); sp_io_stream_reader_t r = sp_zero; sp_io_stream_reader_from_file(&r, file, SP_IO_CLOSE_MODE_AUTO); sp_io_file_writer_t w = sp_zero; - sp_io_file_writer_from_path(&w, dst_path); + sp_io_file_writer_from_path_at(&w, dst_path); u64 copied = 0; EXPECT_EQ(sp_io_copy(&w.base, &r.base, &copied), SP_OK); @@ -419,7 +419,7 @@ UTEST_F(io, stream_file_to_file_copy) { sp_io_file_writer_close(&w); sp_str_t loaded = sp_zero; - sp_io_read_file(ut.mem, dst_path, &loaded); + sp_io_read_file_at(ut.mem, dst_path, &loaded); EXPECT_EQ(loaded.len, sizeof(source)); sp_for(i, sizeof(source)) EXPECT_EQ((u8)loaded.data[i], source[i]); } @@ -435,7 +435,7 @@ UTEST_F(io, file_copy_fast_path_falls_back_for_mem_source) { EXPECT_TRUE(r.as_file == SP_NULLPTR); // Mem reader has no file to hand out. sp_io_file_writer_t w = sp_zero; - sp_io_file_writer_from_path(&w, ut.file_path); + sp_io_file_writer_from_path_at(&w, ut.file); u64 copied = 0; EXPECT_EQ(sp_io_copy(&w.base, &r, &copied), SP_OK); @@ -444,7 +444,7 @@ UTEST_F(io, file_copy_fast_path_falls_back_for_mem_source) { sp_io_file_writer_close(&w); sp_str_t loaded = sp_zero; - sp_io_read_file(ut.mem, ut.file_path, &loaded); + sp_io_read_file_at(ut.mem, ut.file, &loaded); EXPECT_EQ(loaded.len, n); sp_for(i, n) EXPECT_EQ(loaded.data[i], content[i]); } @@ -457,13 +457,13 @@ UTEST_F(io, read_file_larger_than_single_read) { const u64 size = (u64)0x7ffff000 + 1; sp_io_file_writer_t w = sp_zero; - ASSERT_EQ(sp_io_file_writer_from_path(&w, ut.file_path), SP_OK); + ASSERT_EQ(sp_io_file_writer_from_path_at(&w, ut.file), SP_OK); ASSERT_EQ(sp_io_file_writer_seek(&w, (s64)(size - 1), SP_IO_SEEK_SET, SP_NULLPTR), SP_OK); ASSERT_EQ(sp_io_write(&w.base, "!", 1, SP_NULLPTR), SP_OK); sp_io_file_writer_close(&w); sp_str_t loaded = sp_zero; - EXPECT_EQ(sp_io_read_file(ut.mem, ut.file_path, &loaded), SP_OK); + EXPECT_EQ(sp_io_read_file_at(ut.mem, ut.file, &loaded), SP_OK); EXPECT_EQ(loaded.len, (u32)size); if (loaded.len == (u32)size) { EXPECT_EQ(loaded.data[size - 1], '!'); @@ -481,12 +481,12 @@ UTEST_F(io, read_file_larger_than_single_read) { // for the size, and the answer is then memoized. UTEST_F(io, file_reader_from_file_defers_stat) { sp_io_file_writer_t w = sp_zero; - sp_io_file_writer_from_path(&w, ut.file_path); + sp_io_file_writer_from_path_at(&w, ut.file); sp_io_write(&w.base, "abc", 3, SP_NULLPTR); sp_io_file_writer_close(&w); sp_sys_fd_t fd = SP_SYS_INVALID_FD; - ASSERT_EQ(sp_sys_open_s(sp_sys_get_root(0), ut.file_path, SP_SYS_OPEN_MODE_RO, 0, &fd), SP_OK); + ASSERT_EQ(sp_sys_open_s(ut.file.dir, ut.file.sub, SP_SYS_OPEN_MODE_RO, 0, &fd), SP_OK); sp_io_file_reader_t r = sp_zero; EXPECT_EQ(sp_io_file_reader_from_file(&r, fd, SP_IO_CLOSE_MODE_AUTO), SP_OK); @@ -501,12 +501,12 @@ UTEST_F(io, file_reader_from_file_defers_stat) { UTEST_F(io, file_writer_from_fd_defers_stat) { sp_io_file_writer_t w = sp_zero; - sp_io_file_writer_from_path(&w, ut.file_path); + sp_io_file_writer_from_path_at(&w, ut.file); sp_io_write(&w.base, "abc", 3, SP_NULLPTR); sp_io_file_writer_close(&w); sp_sys_fd_t fd = SP_SYS_INVALID_FD; - ASSERT_EQ(sp_sys_open_s(sp_sys_get_root(0), ut.file_path, SP_SYS_OPEN_MODE_WO, 0, &fd), SP_OK); + ASSERT_EQ(sp_sys_open_s(ut.file.dir, ut.file.sub, SP_SYS_OPEN_MODE_WO, 0, &fd), SP_OK); sp_io_file_writer_t a = sp_zero; EXPECT_EQ(sp_io_file_writer_from_fd(&a, fd, SP_IO_CLOSE_MODE_AUTO), SP_OK); @@ -524,12 +524,12 @@ UTEST_F(io, file_writer_from_fd_defers_stat) { // size_force is the explicit refresh. UTEST_F(io, file_reader_size_is_memoized) { sp_io_file_writer_t w = sp_zero; - sp_io_file_writer_from_path(&w, ut.file_path); + sp_io_file_writer_from_path_at(&w, ut.file); sp_io_write(&w.base, "abc", 3, SP_NULLPTR); sp_io_flush(&w.base); sp_io_file_reader_t r = sp_zero; - sp_io_file_reader_from_path(&r, ut.file_path); + sp_io_file_reader_from_path_at(&r, ut.file); EXPECT_FALSE(r.size_known); u64 size = 0; @@ -553,7 +553,7 @@ UTEST_F(io, file_reader_size_is_memoized) { // asking; writes then keep the size current. UTEST_F(io, file_writer_from_path_knows_size) { sp_io_file_writer_t w = sp_zero; - sp_io_file_writer_from_path(&w, ut.file_path); + sp_io_file_writer_from_path_at(&w, ut.file); EXPECT_TRUE(w.size_known); EXPECT_EQ(w.size, (u64)0); @@ -572,12 +572,12 @@ UTEST_F(io, file_writer_from_path_knows_size) { UTEST_F(io, file_reader_positional_does_not_touch_kernel_cursor) { SKIP_ON_WIN32() sp_io_file_writer_t w = sp_zero; - sp_io_file_writer_from_path(&w, ut.file_path); + sp_io_file_writer_from_path_at(&w, ut.file); sp_io_write(&w.base, "0123456789ABCDEF", 16, SP_NULLPTR); sp_io_file_writer_close(&w); sp_sys_fd_t fd = SP_SYS_INVALID_FD; - sp_sys_open_s(sp_sys_get_root(0), ut.file_path, SP_SYS_OPEN_MODE_RO, 0, &fd); + sp_sys_open_s(ut.file.dir, ut.file.sub, SP_SYS_OPEN_MODE_RO, 0, &fd); const s64 parked = 7; sp_sys_lseek(fd, parked, SP_IO_SEEK_SET); @@ -599,7 +599,7 @@ UTEST_F(io, file_seek_beyond_4gb) { u8 marker [4] = {0xDE, 0xAD, 0xBE, 0xEF}; sp_io_file_writer_t w = sp_zero; - sp_io_file_writer_from_path(&w, ut.file_path); + sp_io_file_writer_from_path_at(&w, ut.file); s64 seek_pos = 0; EXPECT_EQ(sp_io_file_writer_seek(&w, offset, SP_IO_SEEK_SET, &seek_pos), SP_OK); EXPECT_EQ(seek_pos, offset); @@ -607,7 +607,7 @@ UTEST_F(io, file_seek_beyond_4gb) { sp_io_file_writer_close(&w); sp_io_file_reader_t r = sp_zero; - sp_io_file_reader_from_path(&r, ut.file_path); + sp_io_file_reader_from_path_at(&r, ut.file); u64 end = 0; EXPECT_EQ(sp_io_file_reader_size(&r, &end), SP_OK); EXPECT_EQ(end, (u64)offset + 4); diff --git a/test/io/io.h b/test/io/io.h index 1673d363..31e91d30 100644 --- a/test/io/io.h +++ b/test/io/io.h @@ -7,7 +7,7 @@ struct io { - sp_str_t file_path; + sp_path_t file; sp_test_file_manager_t file_manager; sp_mem_arena_t* arena; sp_mem_t mem; @@ -18,11 +18,14 @@ UTEST_F_SETUP(io) { ut.arena = sp_mem_arena_new(sp_mem_os_new()); ut.mem = sp_mem_arena_as_allocator(ut.arena); sp_test_file_manager_init(&ut.file_manager); - ut.file_path = sp_test_file_create_empty(&ut.file_manager, sp_str_lit("sp_io_rw.file")); + ut.file.sub = sp_str_lit("sp_io_rw.file"); + sp_test_file_create_empty(&ut.file_manager, ut.file.sub); + sp_sys_open_dir_s(sp_sys_get_root(0), ut.file_manager.paths.test, &ut.file.dir); } UTEST_F_TEARDOWN(io) { SKIP_ON_WASM() + sp_sys_close(ut.file.dir); sp_test_file_manager_cleanup(&ut.file_manager); sp_mem_arena_destroy(ut.arena); } diff --git a/test/io/seeking_reader.c b/test/io/seeking_reader.c index a42525df..9f487045 100644 --- a/test/io/seeking_reader.c +++ b/test/io/seeking_reader.c @@ -99,16 +99,16 @@ typedef struct { io_step_t steps [IO_MAX_STEPS]; } io_seeking_reader_file_test_t; -void run_io_seeking_reader_file_test(int* utest_result, sp_str_t path, io_seeking_reader_file_test_t t) { +void run_io_seeking_reader_file_test(int* utest_result, sp_path_t path, io_seeking_reader_file_test_t t) { { sp_io_file_writer_t w = sp_zero; - sp_io_file_writer_from_path(&w, path); + sp_io_file_writer_from_path_at(&w, path); if (t.content) sp_io_write(&w.base, t.content, sp_cstr_len(t.content), SP_NULLPTR); sp_io_file_writer_close(&w); } sp_io_file_reader_t fr = sp_zero; - sp_io_file_reader_from_path(&fr, path); + sp_io_file_reader_from_path_at(&fr, path); u8 wrapper_buf [64] = sp_zero; if (t.buffer) sp_io_reader_set_buffer(&fr.base, wrapper_buf, t.buffer); @@ -155,7 +155,7 @@ void run_io_seeking_reader_file_test(int* utest_result, sp_str_t path, io_seekin } UTEST_F(io, seeking_reader_file_seek) { - run_io_seeking_reader_file_test(utest_result, ut.file_path, (io_seeking_reader_file_test_t){ + run_io_seeking_reader_file_test(utest_result, ut.file, (io_seeking_reader_file_test_t){ .content = "0123456789ABCDEF", .steps = { { .kind = IO_STEP_SEEK, .seek = { 5, SP_IO_SEEK_SET, SP_OK, 5 } }, @@ -165,7 +165,7 @@ UTEST_F(io, seeking_reader_file_seek) { } UTEST_F(io, seeking_reader_file_seek_whence) { - run_io_seeking_reader_file_test(utest_result, ut.file_path, (io_seeking_reader_file_test_t){ + run_io_seeking_reader_file_test(utest_result, ut.file, (io_seeking_reader_file_test_t){ .content = "0123456789ABCDEF", .steps = { { .kind = IO_STEP_SEEK, .seek = { 4, SP_IO_SEEK_SET, SP_OK, 4 } }, @@ -179,7 +179,7 @@ UTEST_F(io, seeking_reader_file_seek_whence) { } UTEST_F(io, seeking_reader_file_seek_invalid) { - run_io_seeking_reader_file_test(utest_result, ut.file_path, (io_seeking_reader_file_test_t){ + run_io_seeking_reader_file_test(utest_result, ut.file, (io_seeking_reader_file_test_t){ .content = "0123456789", .steps = { { .kind = IO_STEP_SEEK, .seek = { -10, SP_IO_SEEK_SET, SP_ERR_IO_SEEK_INVALID, -1 } }, @@ -188,7 +188,7 @@ UTEST_F(io, seeking_reader_file_seek_invalid) { } UTEST_F(io, seeking_reader_file_seek_buffered) { - run_io_seeking_reader_file_test(utest_result, ut.file_path, (io_seeking_reader_file_test_t){ + run_io_seeking_reader_file_test(utest_result, ut.file, (io_seeking_reader_file_test_t){ .content = "0123456789ABCDEF", .buffer = 8, .steps = { From 45dc3b3441052e77f3315d69a76bf8837e0bdaa8 Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Fri, 18 Sep 2026 17:18:31 -0400 Subject: [PATCH 02/72] delete sp_fs_link --- sp.h | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/sp.h b/sp.h index 62fe3544..f5b98132 100644 --- a/sp.h +++ b/sp.h @@ -3018,11 +3018,6 @@ SP_API void sp_tm_epoch_to_iso8601_w(sp_io_writer_t* io, sp_tm_epoc #define SP_PATH_MAX 4096 #define SP_FS_IT_BUF_SIZE SP_SYS_DIR_MIN_BUF -typedef enum { - SP_FS_LINK_HARD, - SP_FS_LINK_SYMBOLIC, -} sp_fs_link_kind_t; - typedef enum { SP_FS_ATOMIC_REPLACE, SP_FS_ATOMIC_EXCLUSIVE, @@ -3132,7 +3127,6 @@ SP_API sp_err_t sp_fs_create_hard_link(sp_str_t target, sp_str_t lin SP_API sp_err_t sp_fs_create_hard_link_at(sp_path_t target, sp_path_t link_path); SP_API sp_err_t sp_fs_create_sym_link(sp_str_t target, sp_str_t link_path); SP_API sp_err_t sp_fs_create_sym_link_at(sp_str_t target, sp_path_t link_path); -SP_API sp_err_t sp_fs_link(sp_str_t from, sp_str_t to, sp_fs_link_kind_t kind); SP_API sp_err_t sp_fs_copy(sp_str_t from, sp_str_t to); SP_API sp_err_t sp_fs_copy_into(sp_str_t from, sp_str_t dir); SP_API sp_err_t sp_fs_copy_file(sp_str_t from, sp_str_t to, sp_fs_atomic_mode_t mode); @@ -20163,14 +20157,6 @@ sp_err_t sp_fs_create_sym_link(sp_str_t target, sp_str_t link_path) { return sp_fs_create_sym_link_at(target, sp_path_at_root(link_path)); } -sp_err_t sp_fs_link(sp_str_t from, sp_str_t to, sp_fs_link_kind_t kind) { - switch (kind) { - case SP_FS_LINK_HARD: return sp_fs_create_hard_link(from, to); - case SP_FS_LINK_SYMBOLIC: return sp_fs_create_sym_link(from, to); - } - SP_UNREACHABLE_RETURN(SP_OK); -} - sp_err_t sp_fs_remove_file_at(sp_path_t path) { return sp_sys_unlink_s(path.dir, path.sub); } From 74e850d959cb3e634458245ead04b29f1bd8d1af Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Fri, 18 Sep 2026 18:52:03 -0400 Subject: [PATCH 03/72] more --- sp.h | 187 +++++++++++++++++++++++++---------------- test/fs/canonicalize.c | 23 ++--- test/fs/copy.c | 31 ++++--- test/fs/it/os.c | 10 ++- test/sys/path.c | 29 ++----- test/sys/vtable.c | 4 +- 6 files changed, 159 insertions(+), 125 deletions(-) diff --git a/sp.h b/sp.h index f5b98132..b55f7da1 100644 --- a/sp.h +++ b/sp.h @@ -1475,7 +1475,7 @@ SP_API sp_err_t sp_sys_nanosleep(const sp_sys_timespec_t* req, sp_sys_timespe SP_API bool sp_sys_futex_wait(u32* addr, u32 expected, const sp_sys_timespec_t* timeout); SP_API void sp_sys_futex_wake(u32* addr); SP_API void sp_sys_futex_wake_all(u32* addr); -SP_API s64 sp_sys_canonicalize_path(const c8* path, u32 len, c8* buf, u64 size); +SP_API s64 sp_sys_canonicalize_path(sp_sys_fd_t fd, const c8* path, u32 len, c8* buf, u64 size); SP_API sp_err_t sp_sys_event_open(sp_sys_event_t* out); SP_API sp_err_t sp_sys_event_signal(sp_sys_event_t event); SP_API sp_err_t sp_sys_event_clear(sp_sys_event_t event); @@ -1528,7 +1528,7 @@ SP_API sp_err_t sp_sys_symlink_s(sp_str_t existing, sp_sys_fd_t to_fd, sp_str SP_API sp_err_t sp_sys_readlink_s(sp_sys_fd_t fd, sp_str_t path, c8* buf, u64 size, sp_str_t* target); SP_API sp_err_t sp_sys_set_file_perms_s(sp_sys_fd_t fd, sp_str_t path, sp_sys_file_perms_t perms); SP_API sp_err_t sp_sys_set_times_s(sp_sys_fd_t fd, sp_str_t path, sp_sys_timespec_t atime, sp_sys_timespec_t mtime); -SP_API s64 sp_sys_canonicalize_path_s(sp_str_t path, c8* buf, u64 size); +SP_API s64 sp_sys_canonicalize_path_s(sp_sys_fd_t fd, sp_str_t path, c8* buf, u64 size); SP_API sp_err_t sp_sys_dir_from_fd(sp_sys_fd_t fd, sp_sys_dir_t* out); SP_API sp_err_t sp_sys_dir_read(sp_sys_dir_t* dir, sp_mem_buffer_t* buf); SP_API sp_err_t sp_sys_dir_parse(sp_sys_dir_t* dir, sp_mem_buffer_t* buf, u64* cursor, sp_sys_dir_entry_t* out); @@ -1575,7 +1575,7 @@ typedef struct { bool (*futex_wait)(u32* addr, u32 expected, const sp_sys_timespec_t* timeout); void (*futex_wake)(u32* addr); void (*futex_wake_all)(u32* addr); - s64 (*canonicalize_path)(const c8* path, u32 len, c8* buf, u64 size); + s64 (*canonicalize_path)(sp_sys_fd_t fd, const c8* path, u32 len, c8* buf, u64 size); sp_err_t (*event_open)(sp_sys_event_t* out); sp_err_t (*event_signal)(sp_sys_event_t event); sp_err_t (*event_clear)(sp_sys_event_t event); @@ -1650,7 +1650,7 @@ SP_API sp_err_t sp_sys_nanosleep_p(const sp_sys_timespec_t* req, sp_sys_times SP_API bool sp_sys_futex_wait_p(u32* addr, u32 expected, const sp_sys_timespec_t* timeout); SP_API void sp_sys_futex_wake_p(u32* addr); SP_API void sp_sys_futex_wake_all_p(u32* addr); -SP_API s64 sp_sys_canonicalize_path_p(const c8* path, u32 len, c8* buf, u64 size); +SP_API s64 sp_sys_canonicalize_path_p(sp_sys_fd_t fd, const c8* path, u32 len, c8* buf, u64 size); SP_API sp_err_t sp_sys_event_open_p(sp_sys_event_t* out); SP_API sp_err_t sp_sys_event_signal_p(sp_sys_event_t event); SP_API sp_err_t sp_sys_event_clear_p(sp_sys_event_t event); @@ -3063,6 +3063,7 @@ typedef struct { typedef struct { sp_mem_t mem; + sp_sys_fd_t dir; sp_fs_entry_t entry; sp_da(sp_fs_it_frame_t) stack; c8 path [SP_PATH_MAX]; @@ -3128,19 +3129,26 @@ SP_API sp_err_t sp_fs_create_hard_link_at(sp_path_t target, sp_path_ SP_API sp_err_t sp_fs_create_sym_link(sp_str_t target, sp_str_t link_path); SP_API sp_err_t sp_fs_create_sym_link_at(sp_str_t target, sp_path_t link_path); SP_API sp_err_t sp_fs_copy(sp_str_t from, sp_str_t to); +SP_API sp_err_t sp_fs_copy_at(sp_path_t from, sp_path_t to); SP_API sp_err_t sp_fs_copy_into(sp_str_t from, sp_str_t dir); +SP_API sp_err_t sp_fs_copy_into_at(sp_path_t from, sp_path_t dir); SP_API sp_err_t sp_fs_copy_file(sp_str_t from, sp_str_t to, sp_fs_atomic_mode_t mode); +SP_API sp_err_t sp_fs_copy_file_at(sp_path_t from, sp_path_t to, sp_fs_atomic_mode_t mode); SP_API sp_err_t sp_fs_copy_tree(sp_str_t from, sp_str_t to, sp_fs_atomic_mode_t mode); +SP_API sp_err_t sp_fs_copy_tree_at(sp_path_t from, sp_path_t to, sp_fs_atomic_mode_t mode); SP_API sp_err_t sp_fs_dir_open(sp_fs_dir_t* it, sp_sys_fd_t fd, sp_str_t path, sp_mem_slice_t buf); SP_API sp_err_t sp_fs_dir_next(sp_fs_dir_t* it, sp_fs_dir_entry_t* out); SP_API sp_err_t sp_fs_dir_close(sp_fs_dir_t* it); SP_API sp_fs_it_t sp_fs_it_new(sp_mem_t mem, sp_str_t path); +SP_API sp_fs_it_t sp_fs_it_new_at(sp_mem_t mem, sp_path_t path); SP_API sp_fs_it_t sp_fs_it_new_recursive(sp_mem_t mem, sp_str_t path); +SP_API sp_fs_it_t sp_fs_it_new_recursive_at(sp_mem_t mem, sp_path_t path); SP_API bool sp_fs_it_next(sp_fs_it_t* it); SP_API void sp_fs_it_deinit(sp_fs_it_t* it); // literally sys+normalize, if normalize isnt needed this isn't either? SP_API sp_str_t sp_fs_canonicalize_path(sp_mem_t mem, sp_str_t path); +SP_API sp_str_t sp_fs_canonicalize_path_at(sp_mem_t mem, sp_path_t path); // this goes in sys SP_API sp_str_t sp_fs_resolve(sp_mem_t mem, sp_sys_fd_t fd); @@ -5965,8 +5973,8 @@ void sp_sys_futex_wake_all(u32* addr) { (sp_rt.vt->futex_wake_all)(addr); } -s64 sp_sys_canonicalize_path(const c8* path, u32 len, c8* buf, u64 size) { - return (sp_rt.vt->canonicalize_path)(path, len, buf, size); +s64 sp_sys_canonicalize_path(sp_sys_fd_t fd, const c8* path, u32 len, c8* buf, u64 size) { + return (sp_rt.vt->canonicalize_path)(fd, path, len, buf, size); } sp_err_t sp_sys_event_open(sp_sys_event_t* out) { @@ -10768,13 +10776,13 @@ sp_err_t sp_sys_set_times_s(sp_sys_fd_t fd, sp_str_t path, sp_sys_timespec_t ati /////////////////////////////// // SP_SYS_CANONICALIZE_PATH // /////////////////////////////// -s64 sp_sys_canonicalize_path_p(const c8* path, u32 len, c8* buf, u64 size) { +s64 sp_sys_canonicalize_path_p(sp_sys_fd_t fd, const c8* path, u32 len, c8* buf, u64 size) { #if defined(SP_WIN32) if (!buf) return -1; sp_sys_fd_t handle = SP_SYS_INVALID_FD; if (!SP_NT_SUCCESS(sp_sys_nt_open( - sp_sys_get_root(0), + fd, sp_str(path, len), FILE_READ_ATTRIBUTES | SYNCHRONIZE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, @@ -10804,15 +10812,15 @@ s64 sp_sys_canonicalize_path_p(const c8* path, u32 len, c8* buf, u64 size) { #elif defined(SP_LINUX) c8 pbuf [SP_PATH_MAX]; sp_try_as(sp_sys_posix_path(path, len, pbuf), -1); - s64 fd = sp_syscall(SP_SYSCALL_NUM_OPENAT, SP_AT_FDCWD, pbuf, SP_SYS_LINUX_O_PATH | SP_SYS_LINUX_O_CLOEXEC, 0); - if (fd < 0) return -1; + s64 file = sp_syscall(SP_SYSCALL_NUM_OPENAT, fd, pbuf, SP_SYS_LINUX_O_PATH | SP_SYS_LINUX_O_CLOEXEC, 0); + if (file < 0) return -1; c8 proc [64] = sp_zero; - sp_fmt_buf(proc, 64, "/proc/self/fd/{}", sp_fmt_int(fd)); + sp_fmt_buf(proc, 64, "/proc/self/fd/{}", sp_fmt_int(file)); c8 rbuf [SP_PATH_MAX]; s64 n = sp_syscall(SP_SYSCALL_NUM_READLINKAT, SP_AT_FDCWD, proc, rbuf, SP_PATH_MAX); - sp_sys_close(fd); + sp_sys_close(file); if (n < 0 || n == SP_PATH_MAX || (u64)n >= size) return -1; sp_mem_copy(buf, rbuf, (u64)n); buf[n] = 0; @@ -10822,8 +10830,14 @@ s64 sp_sys_canonicalize_path_p(const c8* path, u32 len, c8* buf, u64 size) { if (!path || !buf || size == 0) return -1; c8 pbuf [SP_PATH_MAX]; sp_try_as(sp_sys_posix_path(path, len, pbuf), -1); + + s32 file = openat((s32)fd, pbuf, O_RDONLY | O_NONBLOCK | O_CLOEXEC); + if (file < 0) return -1; + c8 resolved [SP_PATH_MAX]; - if (!realpath(pbuf, resolved)) return -1; + s32 rc = fcntl(file, F_GETPATH, resolved); + sp_sys_close(file); + if (rc < 0) return -1; u64 n = sp_cstr_len(resolved); if (n >= size) return -1; sp_mem_copy(buf, resolved, n); @@ -10831,19 +10845,16 @@ s64 sp_sys_canonicalize_path_p(const c8* path, u32 len, c8* buf, u64 size) { return (s64)n; #elif defined(SP_WASM) - if (!path || !buf || size == 0) return -1; - u64 n = 0; - while (n < len && n < size - 1) { buf[n] = path[n]; n++; } - buf[n] = '\0'; - return (s64)n; + (void)fd; (void)path; (void)len; (void)buf; (void)size; + return -1; #else #error "sp_sys_canonicalize_path" #endif } -s64 sp_sys_canonicalize_path_s(sp_str_t path, c8* buf, u64 size) { - return sp_sys_canonicalize_path(path.data, path.len, buf, size); +s64 sp_sys_canonicalize_path_s(sp_sys_fd_t fd, sp_str_t path, c8* buf, u64 size) { + return sp_sys_canonicalize_path(fd, path.data, path.len, buf, size); } ///////////////////////// @@ -10877,12 +10888,12 @@ s64 sp_sys_get_exe_path_p(c8* buf, u64 size) { c8 raw[4096]; u32 raw_size = sizeof(raw); if (_NSGetExecutablePath(raw, &raw_size)) return -1; - return sp_sys_canonicalize_path(raw, sp_cstr_len(raw), buf, size); + return sp_sys_canonicalize_path(sp_sys_get_root(0), raw, sp_cstr_len(raw), buf, size); #elif defined(SP_COSMO) if (!buf || size == 0) return -1; extern char* program_invocation_name; - return sp_sys_canonicalize_path(program_invocation_name, sp_cstr_len(program_invocation_name), buf, size); + return sp_sys_canonicalize_path(sp_sys_get_root(0), program_invocation_name, sp_cstr_len(program_invocation_name), buf, size); #elif defined(SP_WASM) (void)buf; (void)size; @@ -19970,16 +19981,20 @@ sp_tm_epoch_t sp_fs_get_mod_time(sp_str_t path) { return sp_fs_get_mod_time_at(sp_path_at_root(path)); } -sp_str_t sp_fs_canonicalize_path(sp_mem_t mem, sp_str_t path) { - if (sp_str_empty(path)) return sp_zero_s(sp_str_t); +sp_str_t sp_fs_canonicalize_path_at(sp_mem_t mem, sp_path_t path) { + if (sp_str_empty(path.sub)) return sp_zero_s(sp_str_t); c8 buf[SP_PATH_MAX]; - s64 len = sp_sys_canonicalize_path_s(path, buf, SP_PATH_MAX); + s64 len = sp_sys_canonicalize_path_s(path.dir, path.sub, buf, SP_PATH_MAX); if (len <= 0) return sp_zero_s(sp_str_t); return sp_fs_normalize_path(mem, sp_str(buf, len)); } +sp_str_t sp_fs_canonicalize_path(sp_mem_t mem, sp_str_t path) { + return sp_fs_canonicalize_path_at(mem, sp_path_at_root(path)); +} + sp_str_t sp_fs_get_exe_path(sp_mem_t mem) { c8 buf[SP_PATH_MAX]; s64 len = sp_sys_get_exe_path(buf, SP_PATH_MAX); @@ -20228,7 +20243,7 @@ SP_PRIVATE sp_err_t sp_fs_it_join(sp_fs_it_t* it, u32 prefix, sp_str_t name, u32 SP_PRIVATE sp_err_t sp_fs_it_push(sp_fs_it_t* it, u32 len) { sp_fs_it_frame_t frame = { .len = len }; - sp_try(sp_fs_dir_open(&frame.dir, sp_sys_get_root(0), sp_str(it->path, len), sp_mem_slice(frame.buf, SP_FS_IT_BUF_SIZE))); + sp_try(sp_fs_dir_open(&frame.dir, it->dir, sp_str(it->path, len), sp_mem_slice(frame.buf, SP_FS_IT_BUF_SIZE))); sp_da_push(it->stack, frame); return SP_OK; } @@ -20262,7 +20277,7 @@ SP_PRIVATE sp_err_t sp_fs_it_step(sp_fs_it_t* it) { // If the OS didn't give us a hint, specifically get it from metadata if (it->entry.kind == SP_FS_KIND_NONE) { - it->entry.kind = sp_fs_get_link_kind(it->entry.path); + it->entry.kind = sp_fs_get_link_kind_at((sp_path_t) { .dir = it->dir, .sub = it->entry.path }); } if (it->recursive && it->entry.kind == SP_FS_KIND_DIR) { @@ -20523,12 +20538,12 @@ sp_err_t sp_fs_write_atomic_cstr(sp_str_t path, const c8* str) { return sp_fs_write_atomic_cstr_at(sp_path_at_root(path), str); } -SP_PRIVATE sp_err_t sp_fs_copy_bytes(sp_str_t from, sp_str_t to, sp_fs_atomic_mode_t mode, sp_sys_file_perms_t perms) { +SP_PRIVATE sp_err_t sp_fs_copy_bytes_at(sp_path_t from, sp_path_t to, sp_fs_atomic_mode_t mode, sp_sys_file_perms_t perms) { sp_io_file_reader_t reader = sp_zero; - sp_try(sp_io_file_reader_from_path(&reader, from)); + sp_try(sp_io_file_reader_from_path_at(&reader, from)); sp_fs_atomic_t af = sp_zero; - sp_err_t err = sp_fs_atomic_create(&af, sp_sys_get_root(0), to, sp_fs_parent_path(to)); + sp_err_t err = sp_fs_atomic_open_at(&af, to); if (err) { sp_io_file_reader_close(&reader); return err; @@ -20544,41 +20559,39 @@ SP_PRIVATE sp_err_t sp_fs_copy_bytes(sp_str_t from, sp_str_t to, sp_fs_atomic_mo return sp_fs_atomic_commit(&af, mode); } -SP_PRIVATE sp_err_t sp_fs_copy_tree_file(sp_str_t from, sp_str_t to, sp_fs_atomic_mode_t mode) { +SP_PRIVATE sp_err_t sp_fs_copy_tree_file_at(sp_path_t from, sp_path_t to, sp_fs_atomic_mode_t mode) { sp_sys_file_meta_t meta = sp_zero; - sp_try(sp_sys_get_path_metadata_s(sp_sys_get_root(0), from, &meta)); - return sp_fs_copy_bytes(from, to, mode, meta.perms); + sp_try(sp_sys_get_path_metadata_s(from.dir, from.sub, &meta)); + return sp_fs_copy_bytes_at(from, to, mode, meta.perms); } -SP_PRIVATE sp_err_t sp_fs_copy_link(sp_str_t from, sp_str_t to, sp_fs_atomic_mode_t mode) { - sp_sys_fd_t root = sp_sys_get_root(0); - +SP_PRIVATE sp_err_t sp_fs_copy_link_at(sp_path_t from, sp_path_t to, sp_fs_atomic_mode_t mode) { c8 buf [SP_PATH_MAX]; sp_str_t target = sp_zero; - sp_try(sp_sys_readlink_s(root, from, buf, sizeof(buf), &target)); + sp_try(sp_sys_readlink_s(from.dir, from.sub, buf, sizeof(buf), &target)); switch (mode) { case SP_FS_ATOMIC_REPLACE: { c8 temp_buf [SP_PATH_MAX]; - sp_str_t temp = sp_fs_temp_name(sp_fs_parent_path(to), sp_fs_get_name(to), temp_buf); - sp_try(sp_sys_symlink_s(target, root, temp)); - sp_err_t err = sp_sys_rename_s(root, temp, root, to); - if (err) sp_sys_unlink_s(root, temp); + sp_str_t temp = sp_fs_temp_name(sp_fs_parent_path(to.sub), sp_fs_get_name(to.sub), temp_buf); + sp_try(sp_sys_symlink_s(target, to.dir, temp)); + sp_err_t err = sp_sys_rename_s(to.dir, temp, to.dir, to.sub); + if (err) sp_sys_unlink_s(to.dir, temp); return err; } case SP_FS_ATOMIC_EXCLUSIVE: { - return sp_sys_symlink_s(target, root, to); + return sp_sys_symlink_s(target, to.dir, to.sub); } } SP_UNREACHABLE_RETURN(SP_OK); } -sp_err_t sp_fs_copy_file(sp_str_t from, sp_str_t to, sp_fs_atomic_mode_t mode) { +sp_err_t sp_fs_copy_file_at(sp_path_t from, sp_path_t to, sp_fs_atomic_mode_t mode) { sp_sys_file_meta_t meta = sp_zero; - sp_try(sp_sys_get_path_metadata_s(sp_sys_get_root(0), from, &meta)); + sp_try(sp_sys_get_path_metadata_s(from.dir, from.sub, &meta)); switch (meta.kind) { - case SP_FS_KIND_FILE: return sp_fs_copy_bytes(from, to, mode, meta.perms); + case SP_FS_KIND_FILE: return sp_fs_copy_bytes_at(from, to, mode, meta.perms); case SP_FS_KIND_DIR: return SP_ERR_SYS_IS_DIR; case SP_FS_KIND_SYMLINK: case SP_FS_KIND_NONE: return SP_ERR_SYS_UNSUPPORTED; @@ -20586,39 +20599,44 @@ sp_err_t sp_fs_copy_file(sp_str_t from, sp_str_t to, sp_fs_atomic_mode_t mode) { SP_UNREACHABLE_RETURN(SP_OK); } +sp_err_t sp_fs_copy_file(sp_str_t from, sp_str_t to, sp_fs_atomic_mode_t mode) { + return sp_fs_copy_file_at(sp_path_at_root(from), sp_path_at_root(to), mode); +} + SP_PRIVATE bool sp_fs_path_within(sp_str_t path, sp_str_t root) { return sp_str_starts_with(path, root) && (path.len == root.len || sp_fs_is_sep(path.data[root.len])); } -SP_PRIVATE bool sp_fs_tree_contains(sp_mem_t mem, sp_str_t from, sp_str_t to) { - sp_str_t root = sp_fs_canonicalize_path(mem, from); +SP_PRIVATE bool sp_fs_tree_contains_at(sp_mem_t mem, sp_path_t from, sp_path_t to) { + sp_str_t root = sp_fs_canonicalize_path_at(mem, from); sp_str_t nearest = sp_zero; - while (sp_str_empty(nearest) && !sp_str_empty(to) && !sp_fs_is_root(to)) { - nearest = sp_fs_canonicalize_path(mem, to); - to = sp_fs_parent_path(to); + while (sp_str_empty(nearest) && !sp_str_empty(to.sub) && !sp_fs_is_root(to.sub)) { + nearest = sp_fs_canonicalize_path_at(mem, to); + to.sub = sp_fs_parent_path(to.sub); } return sp_fs_path_within(nearest, root); } -sp_err_t sp_fs_copy_tree(sp_str_t from, sp_str_t to, sp_fs_atomic_mode_t mode) { - from = sp_fs_trim_path(from); - to = sp_fs_trim_path(to); - u32 skip = from.len + !sp_fs_is_sep(sp_str_back(from)); +sp_err_t sp_fs_copy_tree_at(sp_path_t from, sp_path_t to, sp_fs_atomic_mode_t mode) { + from.sub = sp_fs_trim_path(from.sub); + to.sub = sp_fs_trim_path(to.sub); + u32 skip = from.sub.len + !sp_fs_is_sep(sp_str_back(from.sub)); sp_mem_arena_marker_t s = sp_mem_begin_scratch(); - sp_fs_it_t it = sp_fs_it_new_recursive(s.mem, from); + sp_fs_it_t it = sp_fs_it_new_recursive_at(s.mem, from); sp_err_t err = it.err; - if (!err && sp_fs_tree_contains(s.mem, from, to)) err = SP_ERR_SYS_INVALID; - if (!err) err = sp_fs_create_dir(to); + if (!err && sp_fs_tree_contains_at(s.mem, from, to)) err = SP_ERR_SYS_INVALID; + if (!err) err = sp_fs_create_dir_at(to); while (!err && sp_fs_it_next(&it)) { - sp_str_t rel = sp_str_sub(it.entry.path, (s32)skip, (s32)(it.entry.path.len - skip)); - sp_str_t dst = sp_fs_join_path(s.mem, to, rel); + sp_path_t src = { .dir = from.dir, .sub = it.entry.path }; + sp_str_t rel = sp_str_sub(src.sub, (s32)skip, (s32)(src.sub.len - skip)); + sp_path_t dst = { .dir = to.dir, .sub = sp_fs_join_path(s.mem, to.sub, rel) }; switch (it.entry.kind) { - case SP_FS_KIND_DIR: err = sp_fs_create_dir(dst); break; - case SP_FS_KIND_FILE: err = sp_fs_copy_tree_file(it.entry.path, dst, mode); break; - case SP_FS_KIND_SYMLINK: err = sp_fs_copy_link(it.entry.path, dst, mode); break; + case SP_FS_KIND_DIR: err = sp_fs_create_dir_at(dst); break; + case SP_FS_KIND_FILE: err = sp_fs_copy_tree_file_at(src, dst, mode); break; + case SP_FS_KIND_SYMLINK: err = sp_fs_copy_link_at(src, dst, mode); break; case SP_FS_KIND_NONE: err = SP_ERR_SYS_UNSUPPORTED; break; } } @@ -20629,6 +20647,10 @@ sp_err_t sp_fs_copy_tree(sp_str_t from, sp_str_t to, sp_fs_atomic_mode_t mode) { return err; } +sp_err_t sp_fs_copy_tree(sp_str_t from, sp_str_t to, sp_fs_atomic_mode_t mode) { + return sp_fs_copy_tree_at(sp_path_at_root(from), sp_path_at_root(to), mode); +} + bool sp_fs_glob_match(sp_str_t name, sp_str_t glob) { u32 n = 0; u32 g = 0; @@ -20659,40 +20681,57 @@ bool sp_fs_glob_match(sp_str_t name, sp_str_t glob) { return g == glob.len; } -sp_err_t sp_fs_copy(sp_str_t from, sp_str_t to) { +sp_err_t sp_fs_copy_at(sp_path_t from, sp_path_t to) { sp_sys_file_meta_t meta = sp_zero; - sp_try(sp_sys_get_path_metadata_s(sp_sys_get_root(0), from, &meta)); + sp_try(sp_sys_get_path_metadata_s(from.dir, from.sub, &meta)); switch (meta.kind) { - case SP_FS_KIND_FILE: return sp_fs_copy_bytes(from, to, SP_FS_ATOMIC_REPLACE, meta.perms); - case SP_FS_KIND_DIR: return sp_fs_copy_tree(from, to, SP_FS_ATOMIC_REPLACE); + case SP_FS_KIND_FILE: return sp_fs_copy_bytes_at(from, to, SP_FS_ATOMIC_REPLACE, meta.perms); + case SP_FS_KIND_DIR: return sp_fs_copy_tree_at(from, to, SP_FS_ATOMIC_REPLACE); case SP_FS_KIND_SYMLINK: case SP_FS_KIND_NONE: return SP_ERR_SYS_UNSUPPORTED; } SP_UNREACHABLE_RETURN(SP_OK); } -sp_err_t sp_fs_copy_into(sp_str_t from, sp_str_t dir) { +sp_err_t sp_fs_copy(sp_str_t from, sp_str_t to) { + return sp_fs_copy_at(sp_path_at_root(from), sp_path_at_root(to)); +} + +sp_err_t sp_fs_copy_into_at(sp_path_t from, sp_path_t dir) { sp_mem_arena_marker_t s = sp_mem_begin_scratch(); - sp_err_t err = sp_fs_copy(from, sp_fs_join_path(s.mem, dir, sp_fs_get_name(sp_fs_trim_path(from)))); + sp_path_t to = { .dir = dir.dir, .sub = sp_fs_join_path(s.mem, dir.sub, sp_fs_get_name(sp_fs_trim_path(from.sub))) }; + sp_err_t err = sp_fs_copy_at(from, to); sp_mem_end_scratch(s); return err; } -sp_fs_it_t sp_fs_it_new(sp_mem_t mem, sp_str_t path) { - sp_fs_it_t it = { .mem = mem }; +sp_err_t sp_fs_copy_into(sp_str_t from, sp_str_t dir) { + return sp_fs_copy_into_at(sp_path_at_root(from), sp_path_at_root(dir)); +} + +sp_fs_it_t sp_fs_it_new_at(sp_mem_t mem, sp_path_t path) { + sp_fs_it_t it = { .mem = mem, .dir = path.dir }; sp_da_init(mem, it.stack); - it.err = sp_fs_it_open(&it, path); + it.err = sp_fs_it_open(&it, path.sub); return it; } -sp_fs_it_t sp_fs_it_new_recursive(sp_mem_t mem, sp_str_t path) { - sp_fs_it_t it = { .mem = mem, .recursive = true }; +sp_fs_it_t sp_fs_it_new(sp_mem_t mem, sp_str_t path) { + return sp_fs_it_new_at(mem, sp_path_at_root(path)); +} + +sp_fs_it_t sp_fs_it_new_recursive_at(sp_mem_t mem, sp_path_t path) { + sp_fs_it_t it = { .mem = mem, .dir = path.dir, .recursive = true }; sp_da_init(mem, it.stack); - it.err = sp_fs_it_open(&it, path); + it.err = sp_fs_it_open(&it, path.sub); return it; } +sp_fs_it_t sp_fs_it_new_recursive(sp_mem_t mem, sp_str_t path) { + return sp_fs_it_new_recursive_at(mem, sp_path_at_root(path)); +} + // // time // diff --git a/test/fs/canonicalize.c b/test/fs/canonicalize.c index a253df48..f446d498 100644 --- a/test/fs/canonicalize.c +++ b/test/fs/canonicalize.c @@ -204,16 +204,17 @@ sp_test_each(fs, canonicalize, test_t, tests) { skip_if_symlinks_needed(t, it->setup); sp_mem_t mem = sp_test_arena(t); - sp_str_t input = sp_str_view(it->input); - if (it->setup[0].path) { - sp_str_t sandbox = sp_test_dir(t); - fs_apply_setup(t, sandbox, it->setup); - input = sp_fs_join_path(mem, sandbox, input); - } + sp_str_t sandbox = sp_test_dir(t); + fs_apply_setup(t, sandbox, it->setup); + + sp_sys_fd_t dir = SP_SYS_INVALID_FD; + sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, &dir)); - sp_str_t result = sp_fs_canonicalize_path(mem, input); + sp_path_t input = { .dir = dir, .sub = sp_str_view(it->input) }; + sp_str_t result = sp_fs_canonicalize_path_at(mem, input); if (it->expect.empty) { + sp_sys_close(dir); if (result.len != 0) sp_test_fail(t, "expected empty, got {}", sp_fmt_str(result)); return SP_OK; } @@ -232,12 +233,14 @@ sp_test_each(fs, canonicalize, test_t, tests) { sp_expect(t, sp_fs_exists(result)); } if (it->expect.idempotent) { - sp_expect_str_eq(t, sp_fs_canonicalize_path(mem, result), result); + sp_path_t again = { .dir = dir, .sub = result }; + sp_expect_str_eq(t, sp_fs_canonicalize_path_at(mem, again), result); } if (it->expect.same_as) { - sp_str_t other = sp_fs_join_path(mem, sp_test_dir(t), sp_str_view(it->expect.same_as)); - sp_expect_str_eq(t, result, sp_fs_canonicalize_path(mem, other)); + sp_path_t other = { .dir = dir, .sub = sp_str_view(it->expect.same_as) }; + sp_expect_str_eq(t, result, sp_fs_canonicalize_path_at(mem, other)); } + sp_sys_close(dir); return SP_OK; } diff --git a/test/fs/copy.c b/test/fs/copy.c index a658f91b..629fa68b 100644 --- a/test/fs/copy.c +++ b/test/fs/copy.c @@ -534,20 +534,23 @@ static const test_t tests [] = { sp_test_each(fs, copy, test_t, tests) { skip_if_symlinks_needed(t, it->setup); - sp_mem_t mem = sp_test_arena(t); sp_str_t sandbox = sp_test_dir(t); fs_apply_setup(t, sandbox, it->setup); - sp_str_t src = sp_fs_join_path(mem, sandbox, sp_cstr_as_str(it->src)); - sp_str_t dst = sp_fs_join_path(mem, sandbox, sp_cstr_as_str(it->dst)); + sp_sys_fd_t dir = SP_SYS_INVALID_FD; + sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, &dir)); + + sp_path_t src = { .dir = dir, .sub = sp_cstr_as_str(it->src) }; + sp_path_t dst = { .dir = dir, .sub = sp_cstr_as_str(it->dst) }; sp_err_t result = SP_OK; switch (it->op) { - case OP_COPY_FILE: result = sp_fs_copy_file(src, dst, it->mode); break; - case OP_COPY_TREE: result = sp_fs_copy_tree(src, dst, it->mode); break; - case OP_COPY: result = sp_fs_copy(src, dst); break; - case OP_COPY_INTO: result = sp_fs_copy_into(src, dst); break; + case OP_COPY_FILE: result = sp_fs_copy_file_at(src, dst, it->mode); break; + case OP_COPY_TREE: result = sp_fs_copy_tree_at(src, dst, it->mode); break; + case OP_COPY: result = sp_fs_copy_at(src, dst); break; + case OP_COPY_INTO: result = sp_fs_copy_into_at(src, dst); break; } + sp_sys_close(dir); sp_expect_err_eq(t, result, it->err); fs_expect_paths(t, sandbox, it->expect); @@ -569,18 +572,22 @@ static const size_test_t sizes [] = { sp_test_each(fs, copy_size, size_test_t, sizes) { sp_mem_t mem = sp_test_arena(t); - sp_str_t from = fs_path_c(t, "A"); - sp_str_t to = fs_path_c(t, "B"); + sp_sys_fd_t dir = SP_SYS_INVALID_FD; + sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sp_test_dir(t), &dir)); + + sp_path_t from = { .dir = dir, .sub = sp_str_lit("A") }; + sp_path_t to = { .dir = dir, .sub = sp_str_lit("B") }; u8* data = sp_alloc_n(mem, u8, it->size); sp_for(i, it->size) { data[i] = (u8)(i * 31 + 7); } - sp_must_ok(t, sp_fs_create_file_slice(from, sp_mem_slice(data, it->size))); - sp_must_ok(t, sp_fs_copy_file(from, to, SP_FS_ATOMIC_REPLACE)); + sp_must_ok(t, sp_fs_create_file_slice_at(from, sp_mem_slice(data, it->size))); + sp_must_ok(t, sp_fs_copy_file_at(from, to, SP_FS_ATOMIC_REPLACE)); sp_str_t copied = sp_zero; - sp_must_ok(t, sp_io_read_file(mem, to, &copied)); + sp_must_ok(t, sp_io_read_file_at(mem, to, &copied)); + sp_sys_close(dir); sp_must_eq(t, copied.len, it->size); sp_expect_mem_eq(t, copied.data, data, it->size); return SP_OK; diff --git a/test/fs/it/os.c b/test/fs/it/os.c index 9f149aa1..961e3a7f 100644 --- a/test/fs/it/os.c +++ b/test/fs/it/os.c @@ -167,8 +167,11 @@ sp_test_each(fs, it, test_t, tests) { sp_str_t sandbox = sp_test_dir(t); fs_apply_setup(t, sandbox, it->setup); - sp_str_t base = sp_fs_join_path(mem, sandbox, sp_str_lit("R")); - sp_str_t root = sp_fmt(mem, "{}/{}", sp_fmt_str(sandbox), sp_fmt_cstr(it->root ? it->root : "R")).value; + sp_sys_fd_t dir = SP_SYS_INVALID_FD; + sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, &dir)); + + sp_str_t base = sp_str_lit("R"); + sp_path_t root = { .dir = dir, .sub = sp_cstr_as_str(it->root ? it->root : "R") }; fs_match_t matches [FS_MAX_PATHS] = sp_zero; u32 n = 0; @@ -178,7 +181,7 @@ sp_test_each(fs, it, test_t, tests) { matches[n++] = (fs_match_t) { .key = sp_fs_join_path(mem, base, sp_cstr_as_str(want->path)), .kind = want->kind }; } - sp_fs_it_t walk = it->recursive ? sp_fs_it_new_recursive(mem, root) : sp_fs_it_new(mem, root); + sp_fs_it_t walk = it->recursive ? sp_fs_it_new_recursive_at(mem, root) : sp_fs_it_new_at(mem, root); while (sp_fs_it_next(&walk)) { sp_expect_str_eq(t, walk.entry.name, sp_fs_get_name(walk.entry.path)); fs_match(t, matches, n, walk.entry.path, walk.entry.kind); @@ -187,6 +190,7 @@ sp_test_each(fs, it, test_t, tests) { sp_expect(t, !sp_fs_it_next(&walk)); sp_expect_err_eq(t, walk.err, it->expect.err); sp_fs_it_deinit(&walk); + sp_sys_close(dir); fs_match_finish(t, matches, n); return SP_OK; diff --git a/test/sys/path.c b/test/sys/path.c index 326c7d0d..f0937cf4 100644 --- a/test/sys/path.c +++ b/test/sys/path.c @@ -446,7 +446,7 @@ sp_test(sys, canonicalize_refuses_overflow) { sp_fs_create_file(path); c8 full [SP_PATH_MAX] = sp_zero; - s64 len = sp_sys_canonicalize_path_s(path, full, sizeof(full)); + s64 len = sp_sys_canonicalize_path_s(sp_sys_get_root(0), path, full, sizeof(full)); if (len <= 0) { sp_test_fail(t, "canonicalize failed with a full-size buffer"); return SP_OK; @@ -454,7 +454,7 @@ sp_test(sys, canonicalize_refuses_overflow) { c8 buf [SP_PATH_MAX]; sp_for(it, sizeof(buf)) buf[it] = (c8)0xAB; - s64 n = sp_sys_canonicalize_path_s(path, buf, (u64)len + 1); + s64 n = sp_sys_canonicalize_path_s(sp_sys_get_root(0), path, buf, (u64)len + 1); if (n != len) { sp_test_fail(t, "exact fit returned {} but expected {}", sp_fmt_int(n), sp_fmt_int(len)); } @@ -466,13 +466,13 @@ sp_test(sys, canonicalize_refuses_overflow) { } sp_for(it, sizeof(buf)) buf[it] = (c8)0xAB; - n = sp_sys_canonicalize_path_s(path, buf, (u64)len); + n = sp_sys_canonicalize_path_s(sp_sys_get_root(0), path, buf, (u64)len); if (n != -1) { sp_test_fail(t, "no room for NUL: returned {} but expected -1", sp_fmt_int(n)); } sp_for(it, sizeof(buf)) buf[it] = (c8)0xAB; - n = sp_sys_canonicalize_path_s(path, buf, (u64)len - 1); + n = sp_sys_canonicalize_path_s(sp_sys_get_root(0), path, buf, (u64)len - 1); if (n != -1) { sp_test_fail(t, "undersized buffer: returned {} but expected -1", sp_fmt_int(n)); } @@ -480,25 +480,6 @@ sp_test(sys, canonicalize_refuses_overflow) { return SP_OK; } -sp_test(sys, canonicalize_ignores_read_permission) { - sp_mem_t mem = sp_test_arena(t); - sp_str_t path = sp_fs_join_path(mem, sp_test_dir(t), sp_str_lit("file.bin")); - sp_fs_create_file(path); - -#if defined(SP_POSIX) - if (geteuid() == 0) return sp_test_skip(t, "running as root"); -#endif - - sp_err_t err = sp_sys_set_file_perms_s(sp_sys_get_root(0), path, sp_zero_s(sp_sys_file_perms_t)); - if (err == SP_ERR_SYS_UNSUPPORTED) return sp_test_skip(t, "set_file_perms not supported"); - sp_try(err); - - c8 buf [SP_PATH_MAX] = sp_zero; - s64 n = sp_sys_canonicalize_path_s(path, buf, sizeof(buf)); - if (n <= 0) sp_test_fail(t, "canonicalize failed on an unreadable file"); - return SP_OK; -} - sp_test(sys, canonicalize_does_not_block_on_fifo) { #if defined(SP_POSIX) sp_mem_t mem = sp_test_arena(t); @@ -509,7 +490,7 @@ sp_test(sys, canonicalize_does_not_block_on_fifo) { if (pid < 0) return sp_test_skip(t, "fork not available"); if (pid == 0) { c8 buf [SP_PATH_MAX] = sp_zero; - _exit(sp_sys_canonicalize_path_s(path, buf, sizeof(buf)) > 0 ? 0 : 1); + _exit(sp_sys_canonicalize_path_s(sp_sys_get_root(0), path, buf, sizeof(buf)) > 0 ? 0 : 1); } s32 status = 0; diff --git a/test/sys/vtable.c b/test/sys/vtable.c index 4faf9c41..77779a41 100644 --- a/test/sys/vtable.c +++ b/test/sys/vtable.c @@ -139,7 +139,7 @@ static void mock_futex_wake_all(u32* addr) { recorded = 69; } -static s64 mock_canonicalize_path(const c8* path, u32 len, c8* buf, u64 size) { +static s64 mock_canonicalize_path(sp_sys_fd_t fd, const c8* path, u32 len, c8* buf, u64 size) { return 69; } @@ -513,7 +513,7 @@ static s64 call_futex_wake_all(void) { } static s64 call_canonicalize_path(void) { - return sp_sys_canonicalize_path(SP_NULLPTR, 0, SP_NULLPTR, 0); + return sp_sys_canonicalize_path(0, SP_NULLPTR, 0, SP_NULLPTR, 0); } static s64 call_event_open(void) { From 645800a45c10effdab54bf19c55d3273bf2188ec Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Fri, 18 Sep 2026 20:37:52 -0400 Subject: [PATCH 04/72] fuck --- sp.h | 197 +++++++++++++++++++++------------------ test/fs/normalize_path.c | 4 +- test/sys/vtable.c | 10 ++ 3 files changed, 117 insertions(+), 94 deletions(-) diff --git a/sp.h b/sp.h index b55f7da1..2a3ba4ff 100644 --- a/sp.h +++ b/sp.h @@ -1453,6 +1453,7 @@ SP_API s64 sp_sys_get_exe_path(c8* buf, u64 size); SP_API s64 sp_sys_get_cwd_path(c8* buf, u64 size); SP_API s64 sp_sys_get_storage_path(c8* buf, u64 size); SP_API s64 sp_sys_get_config_path(c8* buf, u64 size); +SP_API s64 sp_sys_get_fd_path(sp_sys_fd_t fd, c8* buf, u64 size); SP_API sp_err_t sp_sys_open(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_open_mode_t mode, u32 flags, sp_sys_fd_t* out); SP_API sp_err_t sp_sys_open_dir(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_fd_t* out); SP_API sp_err_t sp_sys_close(sp_sys_fd_t fd); @@ -1553,6 +1554,7 @@ typedef struct { s64 (*get_cwd_path)(c8* buf, u64 size); s64 (*get_storage_path)(c8* buf, u64 size); s64 (*get_config_path)(c8* buf, u64 size); + s64 (*get_fd_path)(sp_sys_fd_t fd, c8* buf, u64 size); sp_err_t (*open)(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_open_mode_t mode, u32 flags, sp_sys_fd_t* out); sp_err_t (*open_dir)(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_fd_t*); sp_err_t (*close)(sp_sys_fd_t fd); @@ -1628,6 +1630,7 @@ SP_API s64 sp_sys_get_exe_path_p(c8* buf, u64 size); SP_API s64 sp_sys_get_cwd_path_p(c8* buf, u64 size); SP_API s64 sp_sys_get_storage_path_p(c8* buf, u64 size); SP_API s64 sp_sys_get_config_path_p(c8* buf, u64 size); +SP_API s64 sp_sys_get_fd_path_p(sp_sys_fd_t fd, c8* buf, u64 size); SP_API sp_err_t sp_sys_open_p(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_open_mode_t mode, u32 flags, sp_sys_fd_t* out); SP_API sp_err_t sp_sys_open_dir_p(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_fd_t* out); SP_API sp_err_t sp_sys_close_p(sp_sys_fd_t fd); @@ -3150,9 +3153,6 @@ SP_API void sp_fs_it_deinit(sp_fs_it_t* it); SP_API sp_str_t sp_fs_canonicalize_path(sp_mem_t mem, sp_str_t path); SP_API sp_str_t sp_fs_canonicalize_path_at(sp_mem_t mem, sp_path_t path); -// this goes in sys -SP_API sp_str_t sp_fs_resolve(sp_mem_t mem, sp_sys_fd_t fd); - // just put these in os SP_API sp_str_t sp_fs_get_cwd(sp_mem_t mem); @@ -5699,6 +5699,7 @@ const sp_sys_vtable_t sp_sys_vtable_platform = { .get_cwd_path = sp_sys_get_cwd_path_p, .get_storage_path = sp_sys_get_storage_path_p, .get_config_path = sp_sys_get_config_path_p, + .get_fd_path = sp_sys_get_fd_path_p, .open = sp_sys_open_p, .open_dir = sp_sys_open_dir_p, .close = sp_sys_close_p, @@ -5885,6 +5886,10 @@ s64 sp_sys_get_config_path(c8* buf, u64 size) { return (sp_rt.vt->get_config_path)(buf, size); } +s64 sp_sys_get_fd_path(sp_sys_fd_t fd, c8* buf, u64 size) { + return (sp_rt.vt->get_fd_path)(fd, buf, size); +} + sp_err_t sp_sys_open(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_open_mode_t mode, u32 flags, sp_sys_fd_t* out) { return (sp_rt.vt->open)(fd, path, len, mode, flags, out); } @@ -6950,6 +6955,15 @@ SP_PRIVATE sp_wide_str_t sp_sys_dos_from_nt_wtf16(u16* nt, u32 len) { return out; } +SP_PRIVATE s64 sp_sys_win32_copy_path(sp_str_t path, c8* buf, u64 size) { + if (path.len >= size) return -1; + sp_for(it, path.len) { + buf[it] = path.data[it] == '\\' ? '/' : path.data[it]; + } + buf[path.len] = 0; + return (s64)path.len; +} + typedef struct { sp_nt_unicode_string_t name; HANDLE root; @@ -10776,10 +10790,54 @@ sp_err_t sp_sys_set_times_s(sp_sys_fd_t fd, sp_str_t path, sp_sys_timespec_t ati /////////////////////////////// // SP_SYS_CANONICALIZE_PATH // /////////////////////////////// -s64 sp_sys_canonicalize_path_p(sp_sys_fd_t fd, const c8* path, u32 len, c8* buf, u64 size) { +s64 sp_sys_get_fd_path_p(sp_sys_fd_t fd, c8* buf, u64 size) { #if defined(SP_WIN32) if (!buf) return -1; + u16 wbuf[SP_PATH_MAX]; + DWORD wlen = GetFinalPathNameByHandleW((HANDLE)fd, (LPWSTR)wbuf, SP_PATH_MAX, 0); + if (wlen == 0 || wlen >= SP_PATH_MAX) return -1; + + sp_wide_str_t wide = sp_sys_dos_from_nt_wtf16(wbuf, wlen); + SP_ALIGNED c8 u8buf[SP_PATH_MAX * 3 + 1]; + sp_mem_fixed_t fixed = sp_mem_fixed(u8buf, sizeof(u8buf)); + sp_str_t utf8; + if (sp_wtf16_to_wtf8(sp_mem_fixed_as_allocator(&fixed), wide, &utf8) != SP_OK) return -1; + return sp_sys_win32_copy_path(utf8, buf, size); + +#elif defined(SP_LINUX) + c8 proc [64] = sp_zero; + sp_fmt_buf(proc, 64, "/proc/self/fd/{}", sp_fmt_int(fd)); + + c8 rbuf [SP_PATH_MAX]; + s64 n = sp_syscall(SP_SYSCALL_NUM_READLINKAT, SP_AT_FDCWD, proc, rbuf, SP_PATH_MAX); + if (n < 0 || n == SP_PATH_MAX || (u64)n >= size) return -1; + sp_mem_copy(buf, rbuf, (u64)n); + buf[n] = 0; + return n; + +#elif defined(SP_MACOS) || defined(SP_COSMO) + if (!buf || size == 0) return -1; + + c8 resolved [SP_PATH_MAX]; + if (fcntl((s32)fd, F_GETPATH, resolved) < 0) return -1; + u64 n = sp_cstr_len(resolved); + if (n >= size) return -1; + sp_mem_copy(buf, resolved, n); + buf[n] = 0; + return (s64)n; + +#elif defined(SP_WASM) + (void)fd; (void)buf; (void)size; + return -1; + +#else + #error "sp_sys_get_fd_path" +#endif +} + +s64 sp_sys_canonicalize_path_p(sp_sys_fd_t fd, const c8* path, u32 len, c8* buf, u64 size) { +#if defined(SP_WIN32) sp_sys_fd_t handle = SP_SYS_INVALID_FD; if (!SP_NT_SUCCESS(sp_sys_nt_open( fd, @@ -10794,20 +10852,9 @@ s64 sp_sys_canonicalize_path_p(sp_sys_fd_t fd, const c8* path, u32 len, c8* buf, return -1; } - u16 wbuf[SP_PATH_MAX]; - DWORD wlen = GetFinalPathNameByHandleW((HANDLE)handle, (LPWSTR)wbuf, SP_PATH_MAX, 0); + s64 n = sp_sys_get_fd_path(handle, buf, size); sp_sys_nt_close(handle); - if (wlen == 0 || wlen >= SP_PATH_MAX) return -1; - - sp_wide_str_t wide = sp_sys_dos_from_nt_wtf16(wbuf, wlen); - SP_ALIGNED c8 u8buf[SP_PATH_MAX * 3 + 1]; - sp_mem_fixed_t fixed = sp_mem_fixed(u8buf, sizeof(u8buf)); - sp_str_t utf8; - if (sp_wtf16_to_wtf8(sp_mem_fixed_as_allocator(&fixed), wide, &utf8) != SP_OK) return -1; - if (utf8.len >= size) return -1; - sp_mem_copy(buf, utf8.data, utf8.len); - buf[utf8.len] = 0; - return (s64)utf8.len; + return n; #elif defined(SP_LINUX) c8 pbuf [SP_PATH_MAX]; @@ -10815,34 +10862,21 @@ s64 sp_sys_canonicalize_path_p(sp_sys_fd_t fd, const c8* path, u32 len, c8* buf, s64 file = sp_syscall(SP_SYSCALL_NUM_OPENAT, fd, pbuf, SP_SYS_LINUX_O_PATH | SP_SYS_LINUX_O_CLOEXEC, 0); if (file < 0) return -1; - c8 proc [64] = sp_zero; - sp_fmt_buf(proc, 64, "/proc/self/fd/{}", sp_fmt_int(file)); - - c8 rbuf [SP_PATH_MAX]; - s64 n = sp_syscall(SP_SYSCALL_NUM_READLINKAT, SP_AT_FDCWD, proc, rbuf, SP_PATH_MAX); - sp_sys_close(file); - if (n < 0 || n == SP_PATH_MAX || (u64)n >= size) return -1; - sp_mem_copy(buf, rbuf, (u64)n); - buf[n] = 0; + s64 n = sp_sys_get_fd_path((sp_sys_fd_t)file, buf, size); + sp_sys_close((sp_sys_fd_t)file); return n; #elif defined(SP_MACOS) || defined(SP_COSMO) - if (!path || !buf || size == 0) return -1; + if (!path) return -1; c8 pbuf [SP_PATH_MAX]; sp_try_as(sp_sys_posix_path(path, len, pbuf), -1); s32 file = openat((s32)fd, pbuf, O_RDONLY | O_NONBLOCK | O_CLOEXEC); if (file < 0) return -1; - c8 resolved [SP_PATH_MAX]; - s32 rc = fcntl(file, F_GETPATH, resolved); + s64 n = sp_sys_get_fd_path(file, buf, size); sp_sys_close(file); - if (rc < 0) return -1; - u64 n = sp_cstr_len(resolved); - if (n >= size) return -1; - sp_mem_copy(buf, resolved, n); - buf[n] = 0; - return (s64)n; + return n; #elif defined(SP_WASM) (void)fd; (void)path; (void)len; (void)buf; (void)size; @@ -10870,10 +10904,7 @@ s64 sp_sys_get_exe_path_p(c8* buf, u64 size) { sp_mem_fixed_t fixed = sp_mem_fixed(u8buf, sizeof(u8buf)); sp_str_t utf8; if (sp_wtf16_to_wtf8(sp_mem_fixed_as_allocator(&fixed), (sp_wide_str_t) { .data = image->Buffer, .len = wlen }, &utf8) != SP_OK) return -1; - if (utf8.len >= size) return -1; - sp_mem_copy(buf, utf8.data, utf8.len); - buf[utf8.len] = 0; - return (s64)utf8.len; + return sp_sys_win32_copy_path(utf8, buf, size); #elif defined(SP_LINUX) c8 rbuf [SP_PATH_MAX]; @@ -10926,10 +10957,7 @@ s64 sp_sys_get_cwd_path_p(c8* buf, u64 size) { utf8.len--; } - if (utf8.len >= size) return -1; - sp_mem_copy(buf, utf8.data, utf8.len); - buf[utf8.len] = 0; - return (s64)utf8.len; + return sp_sys_win32_copy_path(utf8, buf, size); #elif defined(SP_LINUX) s64 n = sp_syscall(SP_SYSCALL_NUM_GETCWD, buf, size); @@ -19854,9 +19882,7 @@ bool sp_fs_is_glob(sp_str_t path) { } sp_str_t sp_fs_normalize_path(sp_mem_t mem, sp_str_t path) { - if (sp_str_back(path) == '/' || sp_str_back(path) == '\\') { - path.len--; - } + path = sp_fs_trim_path(path); c8* buffer = (c8*)sp_mem_allocator_alloc(mem, path.len); sp_for(i, path.len) { @@ -19988,7 +20014,7 @@ sp_str_t sp_fs_canonicalize_path_at(sp_mem_t mem, sp_path_t path) { s64 len = sp_sys_canonicalize_path_s(path.dir, path.sub, buf, SP_PATH_MAX); if (len <= 0) return sp_zero_s(sp_str_t); - return sp_fs_normalize_path(mem, sp_str(buf, len)); + return sp_str_copy(mem, sp_str(buf, len)); } sp_str_t sp_fs_canonicalize_path(sp_mem_t mem, sp_str_t path) { @@ -19999,48 +20025,14 @@ sp_str_t sp_fs_get_exe_path(sp_mem_t mem) { c8 buf[SP_PATH_MAX]; s64 len = sp_sys_get_exe_path(buf, SP_PATH_MAX); if (len <= 0) return sp_str_lit(""); - return sp_fs_normalize_path(mem, sp_str(buf, len)); + return sp_str_copy(mem, sp_str(buf, len)); } sp_str_t sp_fs_get_cwd(sp_mem_t mem) { c8 buf[SP_PATH_MAX]; s64 len = sp_sys_get_cwd_path(buf, SP_PATH_MAX); if (len <= 0) return sp_zero_s(sp_str_t); - return sp_fs_normalize_path(mem, sp_str(buf, len)); -} - -sp_str_t sp_fs_resolve(sp_mem_t mem, sp_sys_fd_t fd) { -#if defined(SP_WIN32) - u16 wbuf[SP_PATH_MAX]; - DWORD wlen = GetFinalPathNameByHandleW((HANDLE)fd, (LPWSTR)wbuf, SP_PATH_MAX, 0); - if (wlen == 0 || wlen >= SP_PATH_MAX) return sp_zero_s(sp_str_t); - u32 skip = 0; - if (wlen >= 4 && wbuf[0] == '\\' && wbuf[1] == '\\' && wbuf[2] == '?' && wbuf[3] == '\\') skip = 4; - sp_str_t resolved; - if (sp_wtf16_to_wtf8(mem, (sp_wide_str_t) { .data = wbuf + skip, .len = wlen - skip }, &resolved) != SP_OK) return sp_zero_s(sp_str_t); - return resolved; - -#elif defined(SP_LINUX) - c8 self [64] = sp_zero; - sp_fmt_buf(self, 64, "/proc/self/fd/{}", sp_fmt_int(fd)); - - c8 buf [SP_PATH_MAX + 1] = sp_zero; - s64 n = sp_syscall(SP_SYSCALL_NUM_READLINKAT, SP_AT_FDCWD, self, buf, SP_PATH_MAX); - if (n <= 0) return sp_zero_s(sp_str_t); - return sp_str_from_cstr_n(mem, buf, n); - -#elif defined(SP_MACOS) || defined(SP_COSMO) - c8 buf[SP_PATH_MAX] = sp_zero; - if (fcntl((int)fd, F_GETPATH, buf) < 0) return sp_zero_s(sp_str_t); - return sp_str_from_cstr(mem, buf); - -#elif defined(SP_WASM) - (void)mem; (void)fd; - return sp_zero_s(sp_str_t); - -#else - #error "sp_fs_resolve" -#endif + return sp_str_copy(mem, sp_str(buf, len)); } sp_str_t sp_fs_get_storage_path(sp_mem_t mem) { @@ -20604,14 +20596,22 @@ sp_err_t sp_fs_copy_file(sp_str_t from, sp_str_t to, sp_fs_atomic_mode_t mode) { } SP_PRIVATE bool sp_fs_path_within(sp_str_t path, sp_str_t root) { - return sp_str_starts_with(path, root) && (path.len == root.len || sp_fs_is_sep(path.data[root.len])); + if (!sp_str_starts_with(path, root)) return false; + return path.len == root.len || sp_fs_is_sep(sp_str_back(root)) || sp_fs_is_sep(path.data[root.len]); } -SP_PRIVATE bool sp_fs_tree_contains_at(sp_mem_t mem, sp_path_t from, sp_path_t to) { - sp_str_t root = sp_fs_canonicalize_path_at(mem, from); +SP_PRIVATE bool sp_fs_tree_contains_at(sp_path_t from, sp_path_t to) { + c8 root_buf [SP_PATH_MAX]; + c8 nearest_buf [SP_PATH_MAX]; + sp_str_t root = sp_zero; sp_str_t nearest = sp_zero; + + s64 len = sp_sys_canonicalize_path_s(from.dir, from.sub, root_buf, SP_PATH_MAX); + if (len > 0) root = sp_str(root_buf, len); + while (sp_str_empty(nearest) && !sp_str_empty(to.sub) && !sp_fs_is_root(to.sub)) { - nearest = sp_fs_canonicalize_path_at(mem, to); + len = sp_sys_canonicalize_path_s(to.dir, to.sub, nearest_buf, SP_PATH_MAX); + if (len > 0) nearest = sp_str(nearest_buf, len); to.sub = sp_fs_parent_path(to.sub); } return sp_fs_path_within(nearest, root); @@ -20626,7 +20626,7 @@ sp_err_t sp_fs_copy_tree_at(sp_path_t from, sp_path_t to, sp_fs_atomic_mode_t mo sp_fs_it_t it = sp_fs_it_new_recursive_at(s.mem, from); sp_err_t err = it.err; - if (!err && sp_fs_tree_contains_at(s.mem, from, to)) err = SP_ERR_SYS_INVALID; + if (!err && sp_fs_tree_contains_at(from, to)) err = SP_ERR_SYS_INVALID; if (!err) err = sp_fs_create_dir_at(to); while (!err && sp_fs_it_next(&it)) { @@ -20634,10 +20634,23 @@ sp_err_t sp_fs_copy_tree_at(sp_path_t from, sp_path_t to, sp_fs_atomic_mode_t mo sp_str_t rel = sp_str_sub(src.sub, (s32)skip, (s32)(src.sub.len - skip)); sp_path_t dst = { .dir = to.dir, .sub = sp_fs_join_path(s.mem, to.sub, rel) }; switch (it.entry.kind) { - case SP_FS_KIND_DIR: err = sp_fs_create_dir_at(dst); break; - case SP_FS_KIND_FILE: err = sp_fs_copy_tree_file_at(src, dst, mode); break; - case SP_FS_KIND_SYMLINK: err = sp_fs_copy_link_at(src, dst, mode); break; - case SP_FS_KIND_NONE: err = SP_ERR_SYS_UNSUPPORTED; break; + case SP_FS_KIND_DIR: { + err = sp_fs_create_dir_at(dst); break; + } + case SP_FS_KIND_FILE: { + sp_sys_file_meta_t meta = sp_zero; + sp_try(sp_sys_get_path_metadata_s(from.dir, from.sub, &meta)); + err = sp_fs_copy_bytes_at(from, to, mode, meta.perms); + break; + } + case SP_FS_KIND_SYMLINK: { + err = sp_fs_copy_link_at(src, dst, mode); + break; + } + case SP_FS_KIND_NONE: { + err = SP_ERR_SYS_UNSUPPORTED; + break; + } } } if (!err) err = it.err; diff --git a/test/fs/normalize_path.c b/test/fs/normalize_path.c index b470c78b..fa25644b 100644 --- a/test/fs/normalize_path.c +++ b/test/fs/normalize_path.c @@ -19,8 +19,8 @@ static const test_t tests [] = { { .name = "mixed_separators", .input = "C:/A\\B/C\\D.txt", .expect = "C:/A/B/C/D.txt" }, { .name = "drive_backslash_trailing", .input = "C:\\A\\B\\", .expect = "C:/A/B" }, { .name = "backslash", .input = "A\\B", .expect = "A/B" }, - { .name = "root_backslash", .input = "\\", .expect = "" }, - { .name = "root_slash", .input = "/", .expect = "" }, + { .name = "root_backslash", .input = "\\", .expect = "/" }, + { .name = "root_slash", .input = "/", .expect = "/" }, { .name = "backslash_trailing", .input = "A\\", .expect = "A" }, { .name = "preserves_dotdot", .input = "A\\B\\..\\C", .expect = "A/B/../C" }, { .name = "preserves_dot", .input = "A\\.\\B", .expect = "A/./B" }, diff --git a/test/sys/vtable.c b/test/sys/vtable.c index 77779a41..19c63e25 100644 --- a/test/sys/vtable.c +++ b/test/sys/vtable.c @@ -53,6 +53,10 @@ static s64 mock_get_config_path(c8* buf, u64 size) { return 69; } +static s64 mock_get_fd_path(sp_sys_fd_t fd, c8* buf, u64 size) { + return 69; +} + static sp_err_t mock_open(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_open_mode_t mode, u32 flags, sp_sys_fd_t* out) { *out = 69; return (sp_err_t)69; @@ -311,6 +315,7 @@ static const sp_sys_vtable_t mock = { .get_cwd_path = mock_get_cwd_path, .get_storage_path = mock_get_storage_path, .get_config_path = mock_get_config_path, + .get_fd_path = mock_get_fd_path, .open = mock_open, .open_dir = mock_open_dir, .close = mock_close, @@ -418,6 +423,10 @@ static s64 call_get_config_path(void) { return sp_sys_get_config_path(SP_NULLPTR, 0); } +static s64 call_get_fd_path(void) { + return sp_sys_get_fd_path(0, SP_NULLPTR, 0); +} + static s64 call_open(void) { sp_sys_fd_t out = SP_SYS_INVALID_FD; sp_sys_open(0, SP_NULLPTR, 0, SP_SYS_OPEN_MODE_RO, 0, &out); @@ -694,6 +703,7 @@ static const test_t tests [] = { { "get_cwd_path", call_get_cwd_path, 69 }, { "get_storage_path", call_get_storage_path, 69 }, { "get_config_path", call_get_config_path, 69 }, + { "get_fd_path", call_get_fd_path, 69 }, { "open", call_open, 69 }, { "open_dir", call_open_dir, 69 }, { "close", call_close, 69 }, From 8266078e1f100ee0ad1ea98a0f1d886593febd94 Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Fri, 18 Sep 2026 20:42:09 -0400 Subject: [PATCH 05/72] canonicalize --- sp.h | 125 ++++++++++++++++------------------------------ test/sys/path.c | 50 ++++--------------- test/sys/vtable.c | 10 ---- 3 files changed, 53 insertions(+), 132 deletions(-) diff --git a/sp.h b/sp.h index 2a3ba4ff..dc500e0d 100644 --- a/sp.h +++ b/sp.h @@ -1403,6 +1403,7 @@ typedef enum { SP_SYS_OPEN_MODE_RO, SP_SYS_OPEN_MODE_WO, SP_SYS_OPEN_MODE_RW, + SP_SYS_OPEN_MODE_PATH, } sp_sys_open_mode_t; typedef enum { @@ -1476,7 +1477,6 @@ SP_API sp_err_t sp_sys_nanosleep(const sp_sys_timespec_t* req, sp_sys_timespe SP_API bool sp_sys_futex_wait(u32* addr, u32 expected, const sp_sys_timespec_t* timeout); SP_API void sp_sys_futex_wake(u32* addr); SP_API void sp_sys_futex_wake_all(u32* addr); -SP_API s64 sp_sys_canonicalize_path(sp_sys_fd_t fd, const c8* path, u32 len, c8* buf, u64 size); SP_API sp_err_t sp_sys_event_open(sp_sys_event_t* out); SP_API sp_err_t sp_sys_event_signal(sp_sys_event_t event); SP_API sp_err_t sp_sys_event_clear(sp_sys_event_t event); @@ -1529,7 +1529,6 @@ SP_API sp_err_t sp_sys_symlink_s(sp_str_t existing, sp_sys_fd_t to_fd, sp_str SP_API sp_err_t sp_sys_readlink_s(sp_sys_fd_t fd, sp_str_t path, c8* buf, u64 size, sp_str_t* target); SP_API sp_err_t sp_sys_set_file_perms_s(sp_sys_fd_t fd, sp_str_t path, sp_sys_file_perms_t perms); SP_API sp_err_t sp_sys_set_times_s(sp_sys_fd_t fd, sp_str_t path, sp_sys_timespec_t atime, sp_sys_timespec_t mtime); -SP_API s64 sp_sys_canonicalize_path_s(sp_sys_fd_t fd, sp_str_t path, c8* buf, u64 size); SP_API sp_err_t sp_sys_dir_from_fd(sp_sys_fd_t fd, sp_sys_dir_t* out); SP_API sp_err_t sp_sys_dir_read(sp_sys_dir_t* dir, sp_mem_buffer_t* buf); SP_API sp_err_t sp_sys_dir_parse(sp_sys_dir_t* dir, sp_mem_buffer_t* buf, u64* cursor, sp_sys_dir_entry_t* out); @@ -1577,7 +1576,6 @@ typedef struct { bool (*futex_wait)(u32* addr, u32 expected, const sp_sys_timespec_t* timeout); void (*futex_wake)(u32* addr); void (*futex_wake_all)(u32* addr); - s64 (*canonicalize_path)(sp_sys_fd_t fd, const c8* path, u32 len, c8* buf, u64 size); sp_err_t (*event_open)(sp_sys_event_t* out); sp_err_t (*event_signal)(sp_sys_event_t event); sp_err_t (*event_clear)(sp_sys_event_t event); @@ -1653,7 +1651,6 @@ SP_API sp_err_t sp_sys_nanosleep_p(const sp_sys_timespec_t* req, sp_sys_times SP_API bool sp_sys_futex_wait_p(u32* addr, u32 expected, const sp_sys_timespec_t* timeout); SP_API void sp_sys_futex_wake_p(u32* addr); SP_API void sp_sys_futex_wake_all_p(u32* addr); -SP_API s64 sp_sys_canonicalize_path_p(sp_sys_fd_t fd, const c8* path, u32 len, c8* buf, u64 size); SP_API sp_err_t sp_sys_event_open_p(sp_sys_event_t* out); SP_API sp_err_t sp_sys_event_signal_p(sp_sys_event_t event); SP_API sp_err_t sp_sys_event_clear_p(sp_sys_event_t event); @@ -5722,7 +5719,6 @@ const sp_sys_vtable_t sp_sys_vtable_platform = { .futex_wait = sp_sys_futex_wait_p, .futex_wake = sp_sys_futex_wake_p, .futex_wake_all = sp_sys_futex_wake_all_p, - .canonicalize_path = sp_sys_canonicalize_path_p, .event_open = sp_sys_event_open_p, .event_signal = sp_sys_event_signal_p, .event_clear = sp_sys_event_clear_p, @@ -5978,10 +5974,6 @@ void sp_sys_futex_wake_all(u32* addr) { (sp_rt.vt->futex_wake_all)(addr); } -s64 sp_sys_canonicalize_path(sp_sys_fd_t fd, const c8* path, u32 len, c8* buf, u64 size) { - return (sp_rt.vt->canonicalize_path)(fd, path, len, buf, size); -} - sp_err_t sp_sys_event_open(sp_sys_event_t* out) { return (sp_rt.vt->event_open)(out); } @@ -8270,6 +8262,7 @@ SP_PRIVATE u32 sp_sys_nt_access_from_mode(sp_sys_open_mode_t mode, u32 flags) { case SP_SYS_OPEN_MODE_RO: { access |= read; break; } case SP_SYS_OPEN_MODE_WO: { access |= write; break; } case SP_SYS_OPEN_MODE_RW: { access |= read | write; break; } + case SP_SYS_OPEN_MODE_PATH: { break; } } return access; } @@ -8289,6 +8282,7 @@ SP_PRIVATE u32 sp_sys_linux_open_flags(sp_sys_open_mode_t mode, u32 flags) { case SP_SYS_OPEN_MODE_RO: { o |= SP_SYS_LINUX_O_RDONLY; break; } case SP_SYS_OPEN_MODE_WO: { o |= SP_SYS_LINUX_O_WRONLY; break; } case SP_SYS_OPEN_MODE_RW: { o |= SP_SYS_LINUX_O_RDWR; break; } + case SP_SYS_OPEN_MODE_PATH: { o |= SP_SYS_LINUX_O_PATH; break; } } if (flags & (SP_SYS_OPEN_CREATE | SP_SYS_OPEN_EXCLUSIVE)) o |= SP_SYS_LINUX_O_CREAT; if (flags & SP_SYS_OPEN_EXCLUSIVE) o |= SP_SYS_LINUX_O_EXCL; @@ -8304,6 +8298,7 @@ SP_PRIVATE s32 sp_sys_posix_open_flags(sp_sys_open_mode_t mode, u32 flags) { case SP_SYS_OPEN_MODE_RO: { o |= O_RDONLY; break; } case SP_SYS_OPEN_MODE_WO: { o |= O_WRONLY; break; } case SP_SYS_OPEN_MODE_RW: { o |= O_RDWR; break; } + case SP_SYS_OPEN_MODE_PATH: { o |= O_RDONLY | O_NONBLOCK; break; } } if (flags & (SP_SYS_OPEN_CREATE | SP_SYS_OPEN_EXCLUSIVE)) o |= O_CREAT; if (flags & SP_SYS_OPEN_EXCLUSIVE) o |= O_EXCL; @@ -8318,15 +8313,17 @@ sp_err_t sp_sys_open_p(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_open_mode *out = SP_SYS_INVALID_FD; if (flags & SP_SYS_OPEN_TRUNCATE) { - if (mode == SP_SYS_OPEN_MODE_RO) return SP_ERR_SYS_INVALID; - if (flags & SP_SYS_OPEN_APPEND) return SP_ERR_SYS_INVALID; + if (mode == SP_SYS_OPEN_MODE_RO) return SP_ERR_SYS_INVALID; + if (mode == SP_SYS_OPEN_MODE_PATH) return SP_ERR_SYS_INVALID; + if (flags & SP_SYS_OPEN_APPEND) return SP_ERR_SYS_INVALID; } #if defined(SP_WIN32) u32 access = sp_sys_nt_access_from_mode(mode, flags); u32 share = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE; u32 disposition = sp_sys_nt_disposition_from_flags(flags); - u32 options = SP_NT_FILE_SYNCHRONOUS_IO_NONALERT | SP_NT_FILE_OPEN_FOR_BACKUP_INTENT | SP_NT_FILE_NON_DIRECTORY_FILE; + u32 options = SP_NT_FILE_SYNCHRONOUS_IO_NONALERT | SP_NT_FILE_OPEN_FOR_BACKUP_INTENT; + if (mode != SP_SYS_OPEN_MODE_PATH) options |= SP_NT_FILE_NON_DIRECTORY_FILE; if (flags & SP_SYS_OPEN_EXCLUSIVE) options |= SP_NT_FILE_OPEN_REPARSE_POINT; sp_sys_fd_t handle = SP_SYS_INVALID_FD; @@ -10787,9 +10784,9 @@ sp_err_t sp_sys_set_times_s(sp_sys_fd_t fd, sp_str_t path, sp_sys_timespec_t ati return sp_sys_set_times(fd, path.data, path.len, atime, mtime); } -/////////////////////////////// -// SP_SYS_CANONICALIZE_PATH // -/////////////////////////////// +//////////////////////// +// SP_SYS_GET_FD_PATH // +//////////////////////// s64 sp_sys_get_fd_path_p(sp_sys_fd_t fd, c8* buf, u64 size) { #if defined(SP_WIN32) if (!buf) return -1; @@ -10836,61 +10833,6 @@ s64 sp_sys_get_fd_path_p(sp_sys_fd_t fd, c8* buf, u64 size) { #endif } -s64 sp_sys_canonicalize_path_p(sp_sys_fd_t fd, const c8* path, u32 len, c8* buf, u64 size) { -#if defined(SP_WIN32) - sp_sys_fd_t handle = SP_SYS_INVALID_FD; - if (!SP_NT_SUCCESS(sp_sys_nt_open( - fd, - sp_str(path, len), - FILE_READ_ATTRIBUTES | SYNCHRONIZE, - FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, - SP_NT_FILE_OPEN, - SP_NT_FILE_SYNCHRONOUS_IO_NONALERT | SP_NT_FILE_OPEN_FOR_BACKUP_INTENT, - 0, - &handle - ))) { - return -1; - } - - s64 n = sp_sys_get_fd_path(handle, buf, size); - sp_sys_nt_close(handle); - return n; - -#elif defined(SP_LINUX) - c8 pbuf [SP_PATH_MAX]; - sp_try_as(sp_sys_posix_path(path, len, pbuf), -1); - s64 file = sp_syscall(SP_SYSCALL_NUM_OPENAT, fd, pbuf, SP_SYS_LINUX_O_PATH | SP_SYS_LINUX_O_CLOEXEC, 0); - if (file < 0) return -1; - - s64 n = sp_sys_get_fd_path((sp_sys_fd_t)file, buf, size); - sp_sys_close((sp_sys_fd_t)file); - return n; - -#elif defined(SP_MACOS) || defined(SP_COSMO) - if (!path) return -1; - c8 pbuf [SP_PATH_MAX]; - sp_try_as(sp_sys_posix_path(path, len, pbuf), -1); - - s32 file = openat((s32)fd, pbuf, O_RDONLY | O_NONBLOCK | O_CLOEXEC); - if (file < 0) return -1; - - s64 n = sp_sys_get_fd_path(file, buf, size); - sp_sys_close(file); - return n; - -#elif defined(SP_WASM) - (void)fd; (void)path; (void)len; (void)buf; (void)size; - return -1; - -#else - #error "sp_sys_canonicalize_path" -#endif -} - -s64 sp_sys_canonicalize_path_s(sp_sys_fd_t fd, sp_str_t path, c8* buf, u64 size) { - return sp_sys_canonicalize_path(fd, path.data, path.len, buf, size); -} - ///////////////////////// // SP_SYS_GET_EXE_PATH // ///////////////////////// @@ -10919,12 +10861,26 @@ s64 sp_sys_get_exe_path_p(c8* buf, u64 size) { c8 raw[4096]; u32 raw_size = sizeof(raw); if (_NSGetExecutablePath(raw, &raw_size)) return -1; - return sp_sys_canonicalize_path(sp_sys_get_root(0), raw, sp_cstr_len(raw), buf, size); + + c8 resolved [SP_PATH_MAX]; + if (!realpath(raw, resolved)) return -1; + u64 n = sp_cstr_len(resolved); + if (n >= size) return -1; + sp_mem_copy(buf, resolved, n); + buf[n] = 0; + return (s64)n; #elif defined(SP_COSMO) if (!buf || size == 0) return -1; extern char* program_invocation_name; - return sp_sys_canonicalize_path(sp_sys_get_root(0), program_invocation_name, sp_cstr_len(program_invocation_name), buf, size); + + c8 resolved [SP_PATH_MAX]; + if (!realpath(program_invocation_name, resolved)) return -1; + u64 n = sp_cstr_len(resolved); + if (n >= size) return -1; + sp_mem_copy(buf, resolved, n); + buf[n] = 0; + return (s64)n; #elif defined(SP_WASM) (void)buf; (void)size; @@ -20007,14 +19963,22 @@ sp_tm_epoch_t sp_fs_get_mod_time(sp_str_t path) { return sp_fs_get_mod_time_at(sp_path_at_root(path)); } -sp_str_t sp_fs_canonicalize_path_at(sp_mem_t mem, sp_path_t path) { +SP_PRIVATE sp_str_t sp_fs_canonicalize_to(sp_path_t path, c8 buf [SP_PATH_MAX]) { if (sp_str_empty(path.sub)) return sp_zero_s(sp_str_t); - c8 buf[SP_PATH_MAX]; - s64 len = sp_sys_canonicalize_path_s(path.dir, path.sub, buf, SP_PATH_MAX); + sp_sys_fd_t fd = SP_SYS_INVALID_FD; + if (sp_sys_open_s(path.dir, path.sub, SP_SYS_OPEN_MODE_PATH, 0, &fd)) return sp_zero_s(sp_str_t); + + s64 len = sp_sys_get_fd_path(fd, buf, SP_PATH_MAX); + sp_sys_close(fd); if (len <= 0) return sp_zero_s(sp_str_t); - return sp_str_copy(mem, sp_str(buf, len)); + return sp_str(buf, len); +} + +sp_str_t sp_fs_canonicalize_path_at(sp_mem_t mem, sp_path_t path) { + c8 buf [SP_PATH_MAX]; + return sp_str_copy(mem, sp_fs_canonicalize_to(path, buf)); } sp_str_t sp_fs_canonicalize_path(sp_mem_t mem, sp_str_t path) { @@ -20603,15 +20567,10 @@ SP_PRIVATE bool sp_fs_path_within(sp_str_t path, sp_str_t root) { SP_PRIVATE bool sp_fs_tree_contains_at(sp_path_t from, sp_path_t to) { c8 root_buf [SP_PATH_MAX]; c8 nearest_buf [SP_PATH_MAX]; - sp_str_t root = sp_zero; + sp_str_t root = sp_fs_canonicalize_to(from, root_buf); sp_str_t nearest = sp_zero; - - s64 len = sp_sys_canonicalize_path_s(from.dir, from.sub, root_buf, SP_PATH_MAX); - if (len > 0) root = sp_str(root_buf, len); - while (sp_str_empty(nearest) && !sp_str_empty(to.sub) && !sp_fs_is_root(to.sub)) { - len = sp_sys_canonicalize_path_s(to.dir, to.sub, nearest_buf, SP_PATH_MAX); - if (len > 0) nearest = sp_str(nearest_buf, len); + nearest = sp_fs_canonicalize_to(to, nearest_buf); to.sub = sp_fs_parent_path(to.sub); } return sp_fs_path_within(nearest, root); diff --git a/test/sys/path.c b/test/sys/path.c index f0937cf4..62b30029 100644 --- a/test/sys/path.c +++ b/test/sys/path.c @@ -440,21 +440,25 @@ static sp_err_t run(sp_test_t* t, test_t* c) { sp_test_each_fn(sys, path, test_t, tests, run); sp_test_each_fn(sys, slash, test_t, slash_tests, run); -sp_test(sys, canonicalize_refuses_overflow) { +sp_test(sys, get_fd_path_refuses_overflow) { sp_mem_t mem = sp_test_arena(t); sp_str_t path = sp_fs_join_path(mem, sp_test_dir(t), sp_str_lit("file.bin")); sp_fs_create_file(path); + sp_sys_fd_t fd = SP_SYS_INVALID_FD; + sp_must_ok(t, sp_sys_open_s(sp_sys_get_root(0), path, SP_SYS_OPEN_MODE_PATH, 0, &fd)); + c8 full [SP_PATH_MAX] = sp_zero; - s64 len = sp_sys_canonicalize_path_s(sp_sys_get_root(0), path, full, sizeof(full)); + s64 len = sp_sys_get_fd_path(fd, full, sizeof(full)); if (len <= 0) { - sp_test_fail(t, "canonicalize failed with a full-size buffer"); + sp_sys_close(fd); + sp_test_fail(t, "get_fd_path failed with a full-size buffer"); return SP_OK; } c8 buf [SP_PATH_MAX]; sp_for(it, sizeof(buf)) buf[it] = (c8)0xAB; - s64 n = sp_sys_canonicalize_path_s(sp_sys_get_root(0), path, buf, (u64)len + 1); + s64 n = sp_sys_get_fd_path(fd, buf, (u64)len + 1); if (n != len) { sp_test_fail(t, "exact fit returned {} but expected {}", sp_fmt_int(n), sp_fmt_int(len)); } @@ -466,49 +470,17 @@ sp_test(sys, canonicalize_refuses_overflow) { } sp_for(it, sizeof(buf)) buf[it] = (c8)0xAB; - n = sp_sys_canonicalize_path_s(sp_sys_get_root(0), path, buf, (u64)len); + n = sp_sys_get_fd_path(fd, buf, (u64)len); if (n != -1) { sp_test_fail(t, "no room for NUL: returned {} but expected -1", sp_fmt_int(n)); } sp_for(it, sizeof(buf)) buf[it] = (c8)0xAB; - n = sp_sys_canonicalize_path_s(sp_sys_get_root(0), path, buf, (u64)len - 1); + n = sp_sys_get_fd_path(fd, buf, (u64)len - 1); if (n != -1) { sp_test_fail(t, "undersized buffer: returned {} but expected -1", sp_fmt_int(n)); } + sp_sys_close(fd); return SP_OK; } - -sp_test(sys, canonicalize_does_not_block_on_fifo) { -#if defined(SP_POSIX) - sp_mem_t mem = sp_test_arena(t); - sp_str_t path = sp_fs_join_path(mem, sp_test_dir(t), sp_str_lit("fifo")); - if (mkfifo(sp_str_to_cstr(mem, path), 0644)) return sp_test_skip(t, "fifos not available"); - - pid_t pid = fork(); - if (pid < 0) return sp_test_skip(t, "fork not available"); - if (pid == 0) { - c8 buf [SP_PATH_MAX] = sp_zero; - _exit(sp_sys_canonicalize_path_s(sp_sys_get_root(0), path, buf, sizeof(buf)) > 0 ? 0 : 1); - } - - s32 status = 0; - sp_for(it, 100) { - if (waitpid(pid, &status, WNOHANG) == pid) { - if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) { - sp_test_fail(t, "canonicalize failed on a fifo"); - } - return SP_OK; - } - sp_os_sleep_ms(5); - } - - sp_test_fail(t, "canonicalize blocked on a fifo with no writer"); - kill(pid, SIGKILL); - waitpid(pid, &status, 0); - return SP_OK; -#else - return sp_test_skip(t, "no fifos"); -#endif -} diff --git a/test/sys/vtable.c b/test/sys/vtable.c index 19c63e25..255c6529 100644 --- a/test/sys/vtable.c +++ b/test/sys/vtable.c @@ -143,10 +143,6 @@ static void mock_futex_wake_all(u32* addr) { recorded = 69; } -static s64 mock_canonicalize_path(sp_sys_fd_t fd, const c8* path, u32 len, c8* buf, u64 size) { - return 69; -} - static sp_err_t mock_event_open(sp_sys_event_t* out) { return (sp_err_t)69; } @@ -337,7 +333,6 @@ static const sp_sys_vtable_t mock = { .futex_wait = mock_futex_wait, .futex_wake = mock_futex_wake, .futex_wake_all = mock_futex_wake_all, - .canonicalize_path = mock_canonicalize_path, .event_open = mock_event_open, .event_signal = mock_event_signal, .event_clear = mock_event_clear, @@ -521,10 +516,6 @@ static s64 call_futex_wake_all(void) { return recorded; } -static s64 call_canonicalize_path(void) { - return sp_sys_canonicalize_path(0, SP_NULLPTR, 0, SP_NULLPTR, 0); -} - static s64 call_event_open(void) { return (s64)sp_sys_event_open(SP_NULLPTR); } @@ -725,7 +716,6 @@ static const test_t tests [] = { { "futex_wait", call_futex_wait, 1 }, { "futex_wake", call_futex_wake, 69 }, { "futex_wake_all", call_futex_wake_all, 69 }, - { "canonicalize_path", call_canonicalize_path, 69 }, { "event_open", call_event_open, 69 }, { "event_signal", call_event_signal, 69 }, { "event_clear", call_event_clear, 69 }, From 4f398ec09601930076b2626b92a70c3f25903aab Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Tue, 22 Sep 2026 17:15:50 -0400 Subject: [PATCH 06/72] 2 --- sp.h | 282 ++++++++++++++++++++-------------------------- test/fs/copy.c | 145 +++++++++++++++++------- test/fs/dir/sim.c | 4 +- test/fs/is_glob.c | 24 ---- test/fs/sim.c | 29 ++--- test/fs/sim.h | 2 +- test/sys/iter.c | 4 +- test/sys/vtable.c | 40 +++---- 8 files changed, 266 insertions(+), 264 deletions(-) delete mode 100644 test/fs/is_glob.c diff --git a/sp.h b/sp.h index dc500e0d..1ab7bd0a 100644 --- a/sp.h +++ b/sp.h @@ -1387,9 +1387,9 @@ typedef struct { } sp_sys_file_meta_t; typedef struct { - s64 handle; - u64 cookie; -} sp_sys_dir_t; + sp_sys_fd_t fd; + s64 state; +} sp_sys_dir_it_t; typedef struct { const c8* name; @@ -1529,10 +1529,10 @@ SP_API sp_err_t sp_sys_symlink_s(sp_str_t existing, sp_sys_fd_t to_fd, sp_str SP_API sp_err_t sp_sys_readlink_s(sp_sys_fd_t fd, sp_str_t path, c8* buf, u64 size, sp_str_t* target); SP_API sp_err_t sp_sys_set_file_perms_s(sp_sys_fd_t fd, sp_str_t path, sp_sys_file_perms_t perms); SP_API sp_err_t sp_sys_set_times_s(sp_sys_fd_t fd, sp_str_t path, sp_sys_timespec_t atime, sp_sys_timespec_t mtime); -SP_API sp_err_t sp_sys_dir_from_fd(sp_sys_fd_t fd, sp_sys_dir_t* out); -SP_API sp_err_t sp_sys_dir_read(sp_sys_dir_t* dir, sp_mem_buffer_t* buf); -SP_API sp_err_t sp_sys_dir_parse(sp_sys_dir_t* dir, sp_mem_buffer_t* buf, u64* cursor, sp_sys_dir_entry_t* out); -SP_API sp_err_t sp_sys_dir_close(sp_sys_dir_t* dir); +SP_API sp_err_t sp_sys_dir_it_open(sp_sys_fd_t fd, sp_sys_dir_it_t* out); +SP_API sp_err_t sp_sys_dir_it_read(sp_sys_dir_it_t* it, sp_mem_buffer_t* buf); +SP_API sp_err_t sp_sys_dir_it_parse(sp_sys_dir_it_t* it, sp_mem_buffer_t* buf, u64* cursor, sp_sys_dir_entry_t* out); +SP_API sp_err_t sp_sys_dir_it_close(sp_sys_dir_it_t* it); SP_API bool sp_sys_is_read_only(sp_sys_file_perms_t perms); SP_API void sp_sys_set_read_only(sp_sys_file_perms_t* perms, bool read_only); SP_API void sp_sys_set_executable(sp_sys_file_perms_t* perms, bool executable); @@ -1611,10 +1611,10 @@ typedef struct { void (*env)(const c8** env, u32* len); s64 (*lseek)(sp_sys_fd_t fd, s64 offset, s32 whence); sp_err_t (*chdir)(const c8* path, u32 len); - sp_err_t (*dir_from_fd)(sp_sys_fd_t fd, sp_sys_dir_t* out); - sp_err_t (*dir_read)(sp_sys_dir_t* dir, sp_mem_buffer_t* buf); - sp_err_t (*dir_parse)(sp_sys_dir_t* dir, sp_mem_buffer_t* buf, u64* cursor, sp_sys_dir_entry_t* out); - sp_err_t (*dir_close)(sp_sys_dir_t* dir); + sp_err_t (*dir_it_open)(sp_sys_fd_t fd, sp_sys_dir_it_t* out); + sp_err_t (*dir_it_read)(sp_sys_dir_it_t* it, sp_mem_buffer_t* buf); + sp_err_t (*dir_it_parse)(sp_sys_dir_it_t* it, sp_mem_buffer_t* buf, u64* cursor, sp_sys_dir_entry_t* out); + sp_err_t (*dir_it_close)(sp_sys_dir_it_t* it); } sp_sys_vtable_t; SP_API sp_err_t sp_sys_read_p(sp_sys_fd_t fd, void* buf, u64 count, u64* bytes_read); @@ -1686,10 +1686,10 @@ SP_API void sp_sys_exit_p(s32 code); SP_API void sp_sys_env_p(const c8** env, u32* len); SP_API s64 sp_sys_lseek_p(sp_sys_fd_t fd, s64 offset, s32 whence); SP_API sp_err_t sp_sys_chdir_p(const c8* path, u32 len); -SP_API sp_err_t sp_sys_dir_from_fd_p(sp_sys_fd_t fd, sp_sys_dir_t* out); -SP_API sp_err_t sp_sys_dir_read_p(sp_sys_dir_t* dir, sp_mem_buffer_t* buf); -SP_API sp_err_t sp_sys_dir_parse_p(sp_sys_dir_t* dir, sp_mem_buffer_t* buf, u64* cursor, sp_sys_dir_entry_t* out); -SP_API sp_err_t sp_sys_dir_close_p(sp_sys_dir_t* dir); +SP_API sp_err_t sp_sys_dir_it_open_p(sp_sys_fd_t fd, sp_sys_dir_it_t* out); +SP_API sp_err_t sp_sys_dir_it_read_p(sp_sys_dir_it_t* it, sp_mem_buffer_t* buf); +SP_API sp_err_t sp_sys_dir_it_parse_p(sp_sys_dir_it_t* it, sp_mem_buffer_t* buf, u64* cursor, sp_sys_dir_entry_t* out); +SP_API sp_err_t sp_sys_dir_it_close_p(sp_sys_dir_it_t* it); SP_API const sp_sys_vtable_t sp_sys_vtable_platform; SP_API const sp_sys_vtable_t* sp_sys_set_vtable(const sp_sys_vtable_t* vt); @@ -3050,7 +3050,7 @@ typedef struct { } sp_fs_dir_entry_t; typedef struct { - sp_sys_dir_t dir; + sp_sys_dir_it_t dir; sp_mem_buffer_t buf; u64 cursor; } sp_fs_dir_t; @@ -3087,8 +3087,6 @@ SP_API bool sp_fs_is_root(sp_str_t path); SP_API bool sp_fs_is_absolute(sp_str_t path); SP_API bool sp_fs_is_absolute_for(sp_str_t path, sp_fs_path_kind_t kind); SP_API bool sp_fs_is_absolute_w(sp_wide_str_t path); -SP_API bool sp_fs_is_glob(sp_str_t path); -SP_API bool sp_fs_glob_match(sp_str_t name, sp_str_t glob); SP_API sp_str_t sp_fs_join_path(sp_mem_t mem, sp_str_t a, sp_str_t b); SP_API sp_str_t sp_fs_replace_ext(sp_mem_t mem, sp_str_t path, sp_str_t ext); SP_API sp_err_t sp_fs_collect(sp_mem_t mem, sp_str_t path, sp_da(sp_fs_entry_t)* out); @@ -3128,13 +3126,8 @@ SP_API sp_err_t sp_fs_create_hard_link(sp_str_t target, sp_str_t lin SP_API sp_err_t sp_fs_create_hard_link_at(sp_path_t target, sp_path_t link_path); SP_API sp_err_t sp_fs_create_sym_link(sp_str_t target, sp_str_t link_path); SP_API sp_err_t sp_fs_create_sym_link_at(sp_str_t target, sp_path_t link_path); -SP_API sp_err_t sp_fs_copy(sp_str_t from, sp_str_t to); -SP_API sp_err_t sp_fs_copy_at(sp_path_t from, sp_path_t to); -SP_API sp_err_t sp_fs_copy_into(sp_str_t from, sp_str_t dir); -SP_API sp_err_t sp_fs_copy_into_at(sp_path_t from, sp_path_t dir); -SP_API sp_err_t sp_fs_copy_file(sp_str_t from, sp_str_t to, sp_fs_atomic_mode_t mode); +SP_API sp_err_t sp_fs_copy_at(sp_path_t from, sp_path_t to, sp_fs_atomic_mode_t mode); SP_API sp_err_t sp_fs_copy_file_at(sp_path_t from, sp_path_t to, sp_fs_atomic_mode_t mode); -SP_API sp_err_t sp_fs_copy_tree(sp_str_t from, sp_str_t to, sp_fs_atomic_mode_t mode); SP_API sp_err_t sp_fs_copy_tree_at(sp_path_t from, sp_path_t to, sp_fs_atomic_mode_t mode); SP_API sp_err_t sp_fs_dir_open(sp_fs_dir_t* it, sp_sys_fd_t fd, sp_str_t path, sp_mem_slice_t buf); SP_API sp_err_t sp_fs_dir_next(sp_fs_dir_t* it, sp_fs_dir_entry_t* out); @@ -5754,10 +5747,10 @@ const sp_sys_vtable_t sp_sys_vtable_platform = { .env = sp_sys_env_p, .lseek = sp_sys_lseek_p, .chdir = sp_sys_chdir_p, - .dir_from_fd = sp_sys_dir_from_fd_p, - .dir_read = sp_sys_dir_read_p, - .dir_parse = sp_sys_dir_parse_p, - .dir_close = sp_sys_dir_close_p, + .dir_it_open = sp_sys_dir_it_open_p, + .dir_it_read = sp_sys_dir_it_read_p, + .dir_it_parse = sp_sys_dir_it_parse_p, + .dir_it_close = sp_sys_dir_it_close_p, }; sp_rt_t sp_rt = { @@ -6142,20 +6135,20 @@ sp_err_t sp_sys_chdir(const c8* path, u32 len) { return (sp_rt.vt->chdir)(path, len); } -sp_err_t sp_sys_dir_from_fd(sp_sys_fd_t fd, sp_sys_dir_t* out) { - return (sp_rt.vt->dir_from_fd)(fd, out); +sp_err_t sp_sys_dir_it_open(sp_sys_fd_t fd, sp_sys_dir_it_t* out) { + return (sp_rt.vt->dir_it_open)(fd, out); } -sp_err_t sp_sys_dir_read(sp_sys_dir_t* dir, sp_mem_buffer_t* buf) { - return (sp_rt.vt->dir_read)(dir, buf); +sp_err_t sp_sys_dir_it_read(sp_sys_dir_it_t* it, sp_mem_buffer_t* buf) { + return (sp_rt.vt->dir_it_read)(it, buf); } -sp_err_t sp_sys_dir_parse(sp_sys_dir_t* dir, sp_mem_buffer_t* buf, u64* cursor, sp_sys_dir_entry_t* out) { - return (sp_rt.vt->dir_parse)(dir, buf, cursor, out); +sp_err_t sp_sys_dir_it_parse(sp_sys_dir_it_t* it, sp_mem_buffer_t* buf, u64* cursor, sp_sys_dir_entry_t* out) { + return (sp_rt.vt->dir_it_parse)(it, buf, cursor, out); } -sp_err_t sp_sys_dir_close(sp_sys_dir_t* dir) { - return (sp_rt.vt->dir_close)(dir); +sp_err_t sp_sys_dir_it_close(sp_sys_dir_it_t* it) { + return (sp_rt.vt->dir_it_close)(it); } const sp_sys_vtable_t* sp_sys_set_vtable(const sp_sys_vtable_t* vt) { @@ -13512,32 +13505,31 @@ SP_PRIVATE sp_fs_kind_t sp_sys_dir_dtype_to_kind(u8 d_type) { } #endif -sp_err_t sp_sys_dir_from_fd_p(sp_sys_fd_t fd, sp_sys_dir_t* out) { - *out = sp_zero_s(sp_sys_dir_t); +sp_err_t sp_sys_dir_it_open_p(sp_sys_fd_t fd, sp_sys_dir_it_t* out) { + *out = sp_zero_s(sp_sys_dir_it_t); + out->fd = fd; #if defined(SP_WIN32) || defined(SP_LINUX) - out->handle = (s64)fd; return SP_OK; #elif defined(SP_MACOS) || defined(SP_COSMO) DIR* dir = fdopendir((int)fd); if (!dir) return sp_sys_err_from_errno(errno); - out->handle = (s64)(intptr_t)dir; + out->state = (s64)(intptr_t)dir; return SP_OK; #elif defined(SP_WASM) - (void)fd; return SP_ERR_SYS_UNSUPPORTED; #else - #error "sp_sys_dir_from_fd" + #error "sp_sys_dir_it_open" #endif } -sp_err_t sp_sys_dir_read_p(sp_sys_dir_t* dir, sp_mem_buffer_t* buf) { +sp_err_t sp_sys_dir_it_read_p(sp_sys_dir_it_t* it, sp_mem_buffer_t* buf) { buf->len = 0; #if defined(SP_WIN32) sp_nt_io_status_block_t iosb = sp_zero; sp_nt_status_t status = SP_NT(NtQueryDirectoryFile)( - (void*)(intptr_t)dir->handle, + (void*)(intptr_t)it->fd, SP_NULLPTR, SP_NULLPTR, SP_NULLPTR, &iosb, buf->data, (u32)(buf->capacity - SP_SYS_DIR_WIN32_SCRATCH), @@ -13549,30 +13541,30 @@ sp_err_t sp_sys_dir_read_p(sp_sys_dir_t* dir, sp_mem_buffer_t* buf) { buf->len = (u64)iosb.Information; return SP_OK; #elif defined(SP_LINUX) - s64 n = sp_lx_getdents64((sp_sys_fd_t)dir->handle, buf->data, buf->capacity); + s64 n = sp_lx_getdents64(it->fd, buf->data, buf->capacity); if (n == -SP_ENOENT) return SP_OK; if (n < 0) return sp_sys_err_from_errno(-n); buf->len = (u64)n; return SP_OK; #elif defined(SP_MACOS) || defined(SP_COSMO) errno = 0; - struct dirent* d = readdir((DIR*)(intptr_t)dir->handle); + struct dirent* d = readdir((DIR*)(intptr_t)it->state); if (!d) return errno ? sp_sys_err_from_errno(errno) : SP_OK; sp_mem_copy(buf->data, d, d->d_reclen); buf->len = d->d_reclen; return SP_OK; #elif defined(SP_WASM) - (void)dir; + (void)it; return SP_ERR_SYS_UNSUPPORTED; #else - #error "sp_sys_dir_read" + #error "sp_sys_dir_it_read" #endif } -sp_err_t sp_sys_dir_parse_p(sp_sys_dir_t* dir, sp_mem_buffer_t* buf, u64* cursor, sp_sys_dir_entry_t* out) { +sp_err_t sp_sys_dir_it_parse_p(sp_sys_dir_it_t* it, sp_mem_buffer_t* buf, u64* cursor, sp_sys_dir_entry_t* out) { *out = sp_zero_s(sp_sys_dir_entry_t); #if defined(SP_WIN32) - (void)dir; + (void)it; sp_nt_file_directory_information_t* d = sp_ptr_cast(sp_nt_file_directory_information_t*, buf->data + *cursor); *cursor = d->NextEntryOffset ? *cursor + d->NextEntryOffset : buf->len; @@ -13586,7 +13578,7 @@ sp_err_t sp_sys_dir_parse_p(sp_sys_dir_t* dir, sp_mem_buffer_t* buf, u64* cursor out->kind = sp_sys_dir_win32_attrs(d->FileAttributes); return SP_OK; #elif defined(SP_LINUX) - (void)dir; + (void)it; sp_sys_dirent64_t* d = sp_ptr_cast(sp_sys_dirent64_t*, buf->data + *cursor); *cursor += d->d_reclen; @@ -13595,7 +13587,7 @@ sp_err_t sp_sys_dir_parse_p(sp_sys_dir_t* dir, sp_mem_buffer_t* buf, u64* cursor out->kind = sp_sys_dir_dtype_to_kind(d->d_type); return SP_OK; #elif defined(SP_MACOS) || defined(SP_COSMO) - (void)dir; + (void)it; struct dirent* d = sp_ptr_cast(struct dirent*, buf->data + *cursor); *cursor += d->d_reclen; @@ -13604,24 +13596,24 @@ sp_err_t sp_sys_dir_parse_p(sp_sys_dir_t* dir, sp_mem_buffer_t* buf, u64* cursor out->kind = sp_sys_dir_dtype_to_kind(d->d_type); return SP_OK; #elif defined(SP_WASM) - (void)dir; (void)buf; (void)cursor; + (void)it; (void)buf; (void)cursor; return SP_ERR_SYS_UNSUPPORTED; #else - #error "sp_sys_dir_parse" + #error "sp_sys_dir_it_parse" #endif } -sp_err_t sp_sys_dir_close_p(sp_sys_dir_t* dir) { +sp_err_t sp_sys_dir_it_close_p(sp_sys_dir_it_t* it) { #if defined(SP_WIN32) || defined(SP_LINUX) - return sp_sys_close((sp_sys_fd_t)dir->handle); + return sp_sys_close(it->fd); #elif defined(SP_MACOS) || defined(SP_COSMO) - if (closedir((DIR*)(intptr_t)dir->handle)) return sp_sys_err_from_errno(errno); + if (closedir((DIR*)(intptr_t)it->state)) return sp_sys_err_from_errno(errno); return SP_OK; #elif defined(SP_WASM) - (void)dir; + (void)it; return SP_ERR_SYS_UNSUPPORTED; #else - #error "sp_sys_dir_close" + #error "sp_sys_dir_it_close" #endif } @@ -19833,10 +19825,6 @@ bool sp_fs_is_absolute_w(sp_wide_str_t path) { return false; } -bool sp_fs_is_glob(sp_str_t path) { - return sp_str_find_c8(path, '*') != SP_STR_NO_MATCH; -} - sp_str_t sp_fs_normalize_path(sp_mem_t mem, sp_str_t path) { path = sp_fs_trim_path(path); @@ -20143,7 +20131,7 @@ sp_err_t sp_fs_dir_open(sp_fs_dir_t* it, sp_sys_fd_t fd, sp_str_t path, sp_mem_s sp_sys_fd_t dir_fd = SP_SYS_INVALID_FD; sp_try(sp_sys_open_dir_s(fd, path, &dir_fd)); - sp_err_t err = sp_sys_dir_from_fd(dir_fd, &it->dir); + sp_err_t err = sp_sys_dir_it_open(dir_fd, &it->dir); if (err) { sp_sys_close(dir_fd); return err; @@ -20160,13 +20148,13 @@ sp_err_t sp_fs_dir_next(sp_fs_dir_t* it, sp_fs_dir_entry_t* out) { while (true) { // Pull another chunk from the kernel and advance the cursor if (it->cursor >= it->buf.len) { - sp_try(sp_sys_dir_read(&it->dir, &it->buf)); + sp_try(sp_sys_dir_it_read(&it->dir, &it->buf)); if (!it->buf.len) return SP_OK; it->cursor = 0; } sp_sys_dir_entry_t entry = sp_zero; - sp_try(sp_sys_dir_parse(&it->dir, &it->buf, &it->cursor, &entry)); + sp_try(sp_sys_dir_it_parse(&it->dir, &it->buf, &it->cursor, &entry)); sp_str_t name = sp_str(entry.name, entry.len); if (sp_str_equal(name, sp_str_lit(".")) || sp_str_equal(name, sp_str_lit(".."))) continue; @@ -20178,7 +20166,7 @@ sp_err_t sp_fs_dir_next(sp_fs_dir_t* it, sp_fs_dir_entry_t* out) { } sp_err_t sp_fs_dir_close(sp_fs_dir_t* it) { - return sp_sys_dir_close(&it->dir); + return sp_sys_dir_it_close(&it->dir); } SP_PRIVATE void sp_fs_it_unwind(sp_fs_it_t* it) { @@ -20515,12 +20503,6 @@ SP_PRIVATE sp_err_t sp_fs_copy_bytes_at(sp_path_t from, sp_path_t to, sp_fs_atom return sp_fs_atomic_commit(&af, mode); } -SP_PRIVATE sp_err_t sp_fs_copy_tree_file_at(sp_path_t from, sp_path_t to, sp_fs_atomic_mode_t mode) { - sp_sys_file_meta_t meta = sp_zero; - sp_try(sp_sys_get_path_metadata_s(from.dir, from.sub, &meta)); - return sp_fs_copy_bytes_at(from, to, mode, meta.perms); -} - SP_PRIVATE sp_err_t sp_fs_copy_link_at(sp_path_t from, sp_path_t to, sp_fs_atomic_mode_t mode) { c8 buf [SP_PATH_MAX]; sp_str_t target = sp_zero; @@ -20555,133 +20537,111 @@ sp_err_t sp_fs_copy_file_at(sp_path_t from, sp_path_t to, sp_fs_atomic_mode_t mo SP_UNREACHABLE_RETURN(SP_OK); } -sp_err_t sp_fs_copy_file(sp_str_t from, sp_str_t to, sp_fs_atomic_mode_t mode) { - return sp_fs_copy_file_at(sp_path_at_root(from), sp_path_at_root(to), mode); +typedef struct { + sp_fs_dir_t from; + sp_sys_fd_t to; + SP_ALIGNED u8 buf [SP_FS_IT_BUF_SIZE]; +} sp_fs_copy_frame_t; + +SP_PRIVATE sp_err_t sp_fs_copy_frame_open(sp_fs_copy_frame_t* frame, sp_path_t from, sp_path_t to) { + sp_try(sp_fs_dir_open(&frame->from, from.dir, from.sub, sp_mem_slice(frame->buf, SP_FS_IT_BUF_SIZE))); + + sp_err_t err = sp_fs_create_dir_at(to); + if (!err) err = sp_sys_open_dir_s(to.dir, to.sub, &frame->to); + if (err) sp_fs_dir_close(&frame->from); + return err; } -SP_PRIVATE bool sp_fs_path_within(sp_str_t path, sp_str_t root) { - if (!sp_str_starts_with(path, root)) return false; - return path.len == root.len || sp_fs_is_sep(sp_str_back(root)) || sp_fs_is_sep(path.data[root.len]); +SP_PRIVATE void sp_fs_copy_frame_close(sp_fs_copy_frame_t* frame) { + sp_fs_dir_close(&frame->from); + sp_sys_close(frame->to); } -SP_PRIVATE bool sp_fs_tree_contains_at(sp_path_t from, sp_path_t to) { - c8 root_buf [SP_PATH_MAX]; - c8 nearest_buf [SP_PATH_MAX]; - sp_str_t root = sp_fs_canonicalize_to(from, root_buf); - sp_str_t nearest = sp_zero; - while (sp_str_empty(nearest) && !sp_str_empty(to.sub) && !sp_fs_is_root(to.sub)) { - nearest = sp_fs_canonicalize_to(to, nearest_buf); - to.sub = sp_fs_parent_path(to.sub); - } - return sp_fs_path_within(nearest, root); +SP_PRIVATE bool sp_fs_is_same_file(const sp_sys_file_meta_t* a, const sp_sys_file_meta_t* b) { + return a->device == b->device && a->id == b->id; } sp_err_t sp_fs_copy_tree_at(sp_path_t from, sp_path_t to, sp_fs_atomic_mode_t mode) { - from.sub = sp_fs_trim_path(from.sub); - to.sub = sp_fs_trim_path(to.sub); - u32 skip = from.sub.len + !sp_fs_is_sep(sp_str_back(from.sub)); + sp_fs_copy_frame_t frame = sp_zero; + sp_try(sp_fs_copy_frame_open(&frame, from, to)); + + sp_sys_file_meta_t source = sp_zero; + sp_sys_file_meta_t root = sp_zero; + sp_err_t err = sp_sys_get_file_metadata(frame.from.dir.fd, &source); + if (!err) err = sp_sys_get_file_metadata(frame.to, &root); + if (!err && sp_fs_is_same_file(&source, &root)) err = SP_ERR_SYS_INVALID; + if (err) { + sp_fs_copy_frame_close(&frame); + return err; + } sp_mem_arena_marker_t s = sp_mem_begin_scratch(); - sp_fs_it_t it = sp_fs_it_new_recursive_at(s.mem, from); + sp_da(sp_fs_copy_frame_t) stack = sp_da_new(s.mem, sp_fs_copy_frame_t); + sp_da_push(stack, frame); - sp_err_t err = it.err; - if (!err && sp_fs_tree_contains_at(from, to)) err = SP_ERR_SYS_INVALID; - if (!err) err = sp_fs_create_dir_at(to); + while (!sp_da_empty(stack)) { + sp_fs_copy_frame_t* top = sp_da_back(stack); + top->from.buf.data = top->buf; - while (!err && sp_fs_it_next(&it)) { - sp_path_t src = { .dir = from.dir, .sub = it.entry.path }; - sp_str_t rel = sp_str_sub(src.sub, (s32)skip, (s32)(src.sub.len - skip)); - sp_path_t dst = { .dir = to.dir, .sub = sp_fs_join_path(s.mem, to.sub, rel) }; - switch (it.entry.kind) { - case SP_FS_KIND_DIR: { - err = sp_fs_create_dir_at(dst); break; - } + sp_fs_dir_entry_t entry = sp_zero; + err = sp_fs_dir_next(&top->from, &entry); + if (err) break; + + if (!entry.name.data) { + sp_fs_copy_frame_close(top); + sp_da_pop(stack); + continue; + } + + sp_path_t src = { .dir = top->from.dir.fd, .sub = entry.name }; + sp_path_t dst = { .dir = top->to, .sub = entry.name }; + sp_sys_file_meta_t meta = sp_zero; + err = sp_sys_get_link_metadata_s(src.dir, src.sub, &meta); + if (err) break; + + switch (meta.kind) { case SP_FS_KIND_FILE: { - sp_sys_file_meta_t meta = sp_zero; - sp_try(sp_sys_get_path_metadata_s(from.dir, from.sub, &meta)); - err = sp_fs_copy_bytes_at(from, to, mode, meta.perms); + err = sp_fs_copy_bytes_at(src, dst, mode, meta.perms); break; } case SP_FS_KIND_SYMLINK: { err = sp_fs_copy_link_at(src, dst, mode); break; } + case SP_FS_KIND_DIR: { + if (sp_fs_is_same_file(&meta, &root)) break; + err = sp_fs_copy_frame_open(&frame, src, dst); + if (!err) sp_da_push(stack, frame); + break; + } case SP_FS_KIND_NONE: { err = SP_ERR_SYS_UNSUPPORTED; break; } } + if (err) break; } - if (!err) err = it.err; - sp_fs_it_deinit(&it); + sp_da_for(stack, it) { + sp_fs_copy_frame_close(&stack[it]); + } sp_mem_end_scratch(s); return err; } -sp_err_t sp_fs_copy_tree(sp_str_t from, sp_str_t to, sp_fs_atomic_mode_t mode) { - return sp_fs_copy_tree_at(sp_path_at_root(from), sp_path_at_root(to), mode); -} - -bool sp_fs_glob_match(sp_str_t name, sp_str_t glob) { - u32 n = 0; - u32 g = 0; - u32 star_n = 0; - u32 star_g = 0; - bool starred = false; - - while (n < name.len) { - if (g < glob.len && glob.data[g] == '*') { - starred = true; - star_g = ++g; - star_n = n; - } - else if (g < glob.len && glob.data[g] == name.data[n]) { - g++; - n++; - } - else if (starred) { - g = star_g; - n = ++star_n; - } - else { - return false; - } - } - - while (g < glob.len && glob.data[g] == '*') g++; - return g == glob.len; -} - -sp_err_t sp_fs_copy_at(sp_path_t from, sp_path_t to) { +sp_err_t sp_fs_copy_at(sp_path_t from, sp_path_t to, sp_fs_atomic_mode_t mode) { sp_sys_file_meta_t meta = sp_zero; sp_try(sp_sys_get_path_metadata_s(from.dir, from.sub, &meta)); switch (meta.kind) { - case SP_FS_KIND_FILE: return sp_fs_copy_bytes_at(from, to, SP_FS_ATOMIC_REPLACE, meta.perms); - case SP_FS_KIND_DIR: return sp_fs_copy_tree_at(from, to, SP_FS_ATOMIC_REPLACE); + case SP_FS_KIND_FILE: return sp_fs_copy_bytes_at(from, to, mode, meta.perms); + case SP_FS_KIND_DIR: return sp_fs_copy_tree_at(from, to, mode); case SP_FS_KIND_SYMLINK: case SP_FS_KIND_NONE: return SP_ERR_SYS_UNSUPPORTED; } SP_UNREACHABLE_RETURN(SP_OK); } -sp_err_t sp_fs_copy(sp_str_t from, sp_str_t to) { - return sp_fs_copy_at(sp_path_at_root(from), sp_path_at_root(to)); -} - -sp_err_t sp_fs_copy_into_at(sp_path_t from, sp_path_t dir) { - sp_mem_arena_marker_t s = sp_mem_begin_scratch(); - sp_path_t to = { .dir = dir.dir, .sub = sp_fs_join_path(s.mem, dir.sub, sp_fs_get_name(sp_fs_trim_path(from.sub))) }; - sp_err_t err = sp_fs_copy_at(from, to); - sp_mem_end_scratch(s); - return err; -} - -sp_err_t sp_fs_copy_into(sp_str_t from, sp_str_t dir) { - return sp_fs_copy_into_at(sp_path_at_root(from), sp_path_at_root(dir)); -} - sp_fs_it_t sp_fs_it_new_at(sp_mem_t mem, sp_path_t path) { sp_fs_it_t it = { .mem = mem, .dir = path.dir }; sp_da_init(mem, it.stack); diff --git a/test/fs/copy.c b/test/fs/copy.c index 629fa68b..6d69f94c 100644 --- a/test/fs/copy.c +++ b/test/fs/copy.c @@ -4,7 +4,6 @@ typedef enum { OP_COPY_FILE, OP_COPY_TREE, OP_COPY, - OP_COPY_INTO, } op_t; typedef struct { @@ -13,6 +12,7 @@ typedef struct { op_t op; sp_fs_atomic_mode_t mode; const c8* src; + const c8* dst_dir; const c8* dst; sp_err_t err; fs_expected_path_t expect [FS_MAX_PATHS]; @@ -66,6 +66,19 @@ static const test_t tests [] = { { .path = "D/E/B", .exists = true, .kind = SP_FS_KIND_FILE, .content = "A" }, }, }, + { + .name = "file_across_dirs", + .setup = { + { .path = "A", .content = "A" }, + { "D", FS_SETUP_DIR }, + }, + .src = "A", + .dst_dir = "D", + .dst = "B", + .expect = { + { .path = "D/B", .exists = true, .kind = SP_FS_KIND_FILE, .content = "A" }, + }, + }, { .name = "file_onto_itself", .setup = { @@ -277,6 +290,49 @@ static const test_t tests [] = { { .path = "E/B", .exists = true, .kind = SP_FS_KIND_DIR }, }, }, + { + .name = "tree_creates_parents", + .setup = { + { "A", FS_SETUP_DIR }, + { .path = "A/B", .content = "B" }, + }, + .op = OP_COPY_TREE, + .src = "A", + .dst = "D/E", + .expect = { + { .path = "D/E/B", .exists = true, .kind = SP_FS_KIND_FILE, .content = "B" }, + }, + }, + { + .name = "tree_across_dirs", + .setup = { + { "A", FS_SETUP_DIR }, + { .path = "A/B", .content = "B" }, + { "D", FS_SETUP_DIR }, + }, + .op = OP_COPY_TREE, + .src = "A", + .dst_dir = "D", + .dst = "E", + .expect = { + { .path = "D/E/B", .exists = true, .kind = SP_FS_KIND_FILE, .content = "B" }, + }, + }, + { + .name = "tree_into_open_dir", + .setup = { + { "A", FS_SETUP_DIR }, + { .path = "A/B", .content = "B" }, + { "D", FS_SETUP_DIR }, + }, + .op = OP_COPY_TREE, + .src = "A", + .dst_dir = "D", + .dst = ".", + .expect = { + { .path = "D/B", .exists = true, .kind = SP_FS_KIND_FILE, .content = "B" }, + }, + }, { .name = "tree_replaces_existing", .setup = { @@ -349,9 +405,9 @@ static const test_t tests [] = { .op = OP_COPY_TREE, .src = "A", .dst = "A/B", - .err = SP_ERR_SYS_INVALID, .expect = { - { .path = "A/B" }, + { .path = "A/B/C", .exists = true, .kind = SP_FS_KIND_FILE, .content = "C" }, + { .path = "A/B/B" }, }, }, { @@ -364,9 +420,39 @@ static const test_t tests [] = { .op = OP_COPY_TREE, .src = "A", .dst = "L/B", - .err = SP_ERR_SYS_INVALID, .expect = { - { .path = "A/B" }, + { .path = "A/B/C", .exists = true, .kind = SP_FS_KIND_FILE, .content = "C" }, + { .path = "A/B/B" }, + }, + }, + { + .name = "tree_into_itself_across_dirs", + .setup = { + { "A", FS_SETUP_DIR }, + { .path = "A/C", .content = "C" }, + }, + .op = OP_COPY_TREE, + .src = "A", + .dst_dir = "A", + .dst = "B", + .expect = { + { .path = "A/B/C", .exists = true, .kind = SP_FS_KIND_FILE, .content = "C" }, + { .path = "A/B/B" }, + }, + }, + { + .name = "tree_into_itself_nested", + .setup = { + { "A", FS_SETUP_DIR }, + { "A/D", FS_SETUP_DIR }, + { .path = "A/D/C", .content = "C" }, + }, + .op = OP_COPY_TREE, + .src = "A", + .dst = "A/D/E", + .expect = { + { .path = "A/D/E/D/C", .exists = true, .kind = SP_FS_KIND_FILE, .content = "C" }, + { .path = "A/D/E/D/E" }, }, }, { @@ -501,34 +587,6 @@ static const test_t tests [] = { { .path = "B" }, }, }, - { - .name = "into_dir", - .setup = { - { "A", FS_SETUP_DIR }, - { .path = "A/B", .content = "B" }, - { "D", FS_SETUP_DIR }, - }, - .op = OP_COPY_INTO, - .src = "A", - .dst = "D", - .expect = { - { .path = "D/A", .exists = true, .kind = SP_FS_KIND_DIR }, - { .path = "D/A/B", .exists = true, .kind = SP_FS_KIND_FILE, .content = "B" }, - }, - }, - { - .name = "into_file", - .setup = { - { .path = "A", .content = "A" }, - { "D", FS_SETUP_DIR }, - }, - .op = OP_COPY_INTO, - .src = "A", - .dst = "D", - .expect = { - { .path = "D/A", .exists = true, .kind = SP_FS_KIND_FILE, .content = "A" }, - }, - }, }; sp_test_each(fs, copy, test_t, tests) { @@ -540,16 +598,19 @@ sp_test_each(fs, copy, test_t, tests) { sp_sys_fd_t dir = SP_SYS_INVALID_FD; sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, &dir)); + sp_sys_fd_t dst_dir = SP_SYS_INVALID_FD; + sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), fs_path_c(t, it->dst_dir ? it->dst_dir : ""), &dst_dir)); + sp_path_t src = { .dir = dir, .sub = sp_cstr_as_str(it->src) }; - sp_path_t dst = { .dir = dir, .sub = sp_cstr_as_str(it->dst) }; + sp_path_t dst = { .dir = dst_dir, .sub = sp_cstr_as_str(it->dst) }; sp_err_t result = SP_OK; switch (it->op) { case OP_COPY_FILE: result = sp_fs_copy_file_at(src, dst, it->mode); break; case OP_COPY_TREE: result = sp_fs_copy_tree_at(src, dst, it->mode); break; - case OP_COPY: result = sp_fs_copy_at(src, dst); break; - case OP_COPY_INTO: result = sp_fs_copy_into_at(src, dst); break; + case OP_COPY: result = sp_fs_copy_at(src, dst, it->mode); break; } + sp_sys_close(dst_dir); sp_sys_close(dir); sp_expect_err_eq(t, result, it->err); @@ -594,6 +655,10 @@ sp_test_each(fs, copy_size, size_test_t, sizes) { } #if defined(SP_POSIX) +static sp_path_t at_root(sp_str_t path) { + return (sp_path_t) { .dir = sp_sys_get_root(0), .sub = path }; +} + // postcondition: dest has source's bytes and mode, on a fresh inode; timestamps unspecified sp_test(fs, copy_file_preserves_mode, .serial = true) { sp_mem_t mem = sp_test_arena(t); @@ -607,7 +672,7 @@ sp_test(fs, copy_file_preserves_mode, .serial = true) { sp_must_eq(t, stat(sp_cstr_from_str(mem, from), &from_stat), 0); mode_t mask = umask(077); - sp_err_t copied_err = sp_fs_copy_file(from, to, SP_FS_ATOMIC_REPLACE); + sp_err_t copied_err = sp_fs_copy_file_at(at_root(from), at_root(to), SP_FS_ATOMIC_REPLACE); umask(mask); sp_must_ok(t, copied_err); @@ -631,7 +696,7 @@ sp_test(fs, copy_file_replaces_read_only_dest) { sp_fs_create_file_str(to, sp_str_lit("B")); sp_must_eq(t, chmod(sp_cstr_from_str(mem, to), 0444), 0); - sp_must_ok(t, sp_fs_copy_file(from, to, SP_FS_ATOMIC_REPLACE)); + sp_must_ok(t, sp_fs_copy_file_at(at_root(from), at_root(to), SP_FS_ATOMIC_REPLACE)); sp_str_t copied = sp_zero; sp_io_read_file(mem, to, &copied); @@ -649,7 +714,7 @@ sp_test(fs, copy_file_unwritable_parent_leaves_nothing) { const c8* dir_c = sp_cstr_from_str(mem, dir); sp_must_eq(t, chmod(dir_c, 0555), 0); - sp_err_t result = sp_fs_copy_file(from, to, SP_FS_ATOMIC_REPLACE); + sp_err_t result = sp_fs_copy_file_at(at_root(from), at_root(to), SP_FS_ATOMIC_REPLACE); sp_must_eq(t, chmod(dir_c, 0755), 0); if (!result) return sp_test_skip(t, "directory permissions not enforced"); @@ -669,7 +734,7 @@ sp_test(fs, copy_tree_unreadable_subdir_fails) { const c8* locked_c = sp_cstr_from_str(mem, locked); sp_must_eq(t, chmod(locked_c, 0), 0); - sp_err_t result = sp_fs_copy_tree(from, to, SP_FS_ATOMIC_REPLACE); + sp_err_t result = sp_fs_copy_tree_at(at_root(from), at_root(to), SP_FS_ATOMIC_REPLACE); sp_must_eq(t, chmod(locked_c, 0755), 0); if (!result) return sp_test_skip(t, "directory permissions not enforced"); diff --git a/test/fs/dir/sim.c b/test/fs/dir/sim.c index efb54b56..c2f608b7 100644 --- a/test/fs/dir/sim.c +++ b/test/fs/dir/sim.c @@ -25,9 +25,9 @@ static const test_t tests [] = { }, }, { - .name = "closes_fd_when_from_fd_fails", + .name = "closes_fd_when_it_open_fails", .dirs = { - { .path = "T", .from_fd = SP_ERR_SYS_IO }, + { .path = "T", .it_open = SP_ERR_SYS_IO }, }, .expect = { .open = SP_ERR_SYS_IO, diff --git a/test/fs/is_glob.c b/test/fs/is_glob.c deleted file mode 100644 index 10a62ea2..00000000 --- a/test/fs/is_glob.c +++ /dev/null @@ -1,24 +0,0 @@ -#include "sp.h" -#include "sp/sp_test.h" - -typedef struct { - const c8* name; - const c8* input; - bool glob; -} test_t; - -static const test_t tests [] = { - { .name = "empty", .input = "" }, - { .name = "plain", .input = "A" }, - { .name = "plain_ext", .input = "A.txt" }, - { .name = "question_not_glob", .input = "A?B" }, - { .name = "star", .input = "*", .glob = true }, - { .name = "star_ext", .input = "*.txt", .glob = true }, - { .name = "star_in_dir", .input = "A/*", .glob = true }, - { .name = "star_infix", .input = "A*B", .glob = true }, -}; - -sp_test_each(fs, is_glob, test_t, tests) { - sp_expect_eq(t, sp_fs_is_glob(sp_str_view(it->input)), it->glob); - return SP_OK; -} diff --git a/test/fs/sim.c b/test/fs/sim.c index 87e59508..e9e43cad 100644 --- a/test/fs/sim.c +++ b/test/fs/sim.c @@ -51,24 +51,25 @@ static sp_err_t close_fd(sp_sys_fd_t fd) { return SP_OK; } -static sp_err_t dir_from_fd(sp_sys_fd_t fd, sp_sys_dir_t* out) { - s64 handle = (s64)fd - FD_BASE; - if (active->opened[handle].dir->from_fd) return active->opened[handle].dir->from_fd; - *out = sp_zero_s(sp_sys_dir_t); - out->handle = handle; +static sp_err_t dir_it_open(sp_sys_fd_t fd, sp_sys_dir_it_t* out) { + s64 slot = (s64)fd - FD_BASE; + if (active->opened[slot].dir->it_open) return active->opened[slot].dir->it_open; + *out = sp_zero_s(sp_sys_dir_it_t); + out->fd = fd; + out->state = slot; return SP_OK; } -static sp_err_t dir_read(sp_sys_dir_t* dir, sp_mem_buffer_t* buf) { - sim_open_t* slot = &active->opened[dir->handle]; +static sp_err_t dir_it_read(sp_sys_dir_it_t* it, sp_mem_buffer_t* buf) { + sim_open_t* slot = &active->opened[it->state]; if (slot->dir->read) return slot->dir->read; buf->len = slot->served ? 0 : dir_len(slot->dir); slot->served = true; return SP_OK; } -static sp_err_t dir_parse(sp_sys_dir_t* dir, sp_mem_buffer_t* buf, u64* cursor, sp_sys_dir_entry_t* out) { - const sim_entry_t* entry = &active->opened[dir->handle].dir->entries[*cursor]; +static sp_err_t dir_it_parse(sp_sys_dir_it_t* it, sp_mem_buffer_t* buf, u64* cursor, sp_sys_dir_entry_t* out) { + const sim_entry_t* entry = &active->opened[it->state].dir->entries[*cursor]; *cursor += 1; *out = sp_zero_s(sp_sys_dir_entry_t); @@ -78,7 +79,7 @@ static sp_err_t dir_parse(sp_sys_dir_t* dir, sp_mem_buffer_t* buf, u64* cursor, return SP_OK; } -static sp_err_t dir_close(sp_sys_dir_t* dir) { +static sp_err_t dir_it_close(sp_sys_dir_it_t* it) { active->count.closes++; return SP_OK; } @@ -95,10 +96,10 @@ void sim_begin(sim_t* sim, const sim_dir_t* dirs) { *sim = (sim_t) { .dirs = dirs, .vt = sp_sys_vtable_platform }; sim->vt.open_dir = open_dir; sim->vt.close = close_fd; - sim->vt.dir_from_fd = dir_from_fd; - sim->vt.dir_read = dir_read; - sim->vt.dir_parse = dir_parse; - sim->vt.dir_close = dir_close; + sim->vt.dir_it_open = dir_it_open; + sim->vt.dir_it_read = dir_it_read; + sim->vt.dir_it_parse = dir_it_parse; + sim->vt.dir_it_close = dir_it_close; sim->vt.get_link_metadata = get_link_metadata; sim->saved = sp_sys_set_vtable(&sim->vt); active = sim; diff --git a/test/fs/sim.h b/test/fs/sim.h index 07404c1c..062042e5 100644 --- a/test/fs/sim.h +++ b/test/fs/sim.h @@ -15,7 +15,7 @@ typedef struct { typedef struct { const c8* path; - sp_err_t from_fd; + sp_err_t it_open; sp_err_t read; sim_entry_t entries [SIM_MAX_ENTRIES]; } sim_dir_t; diff --git a/test/sys/iter.c b/test/sys/iter.c index bc12e304..e0635432 100644 --- a/test/sys/iter.c +++ b/test/sys/iter.c @@ -115,7 +115,7 @@ sp_test_each(sys, iter, sys_iter_case_t, sys_iter_cases) { }; buf.len = sys_iter_pack(raw, it->raw); - sp_sys_dir_t dir = sp_zero; + sp_sys_dir_it_t dir = sp_zero; u64 cursor = 0; sp_carr_for(it->raw, n) { @@ -129,7 +129,7 @@ sp_test_each(sys, iter, sys_iter_case_t, sys_iter_cases) { sp_test_kv_c(t, "entry", it->raw[n].name); sp_sys_dir_entry_t entry = sp_zero; - sp_must_ok(t, sp_sys_dir_parse(&dir, &buf, &cursor, &entry)); + sp_must_ok(t, sp_sys_dir_it_parse(&dir, &buf, &cursor, &entry)); sp_expect_str_eq(t, sp_str(entry.name, entry.len), sp_cstr_as_str(it->raw[n].name)); sp_expect_eq(t, (u32)entry.kind, (u32)it->raw[n].kind); diff --git a/test/sys/vtable.c b/test/sys/vtable.c index 255c6529..5eedb80d 100644 --- a/test/sys/vtable.c +++ b/test/sys/vtable.c @@ -283,19 +283,19 @@ static sp_err_t mock_chdir(const c8* path, u32 len) { return (sp_err_t)69; } -static sp_err_t mock_dir_from_fd(sp_sys_fd_t fd, sp_sys_dir_t* out) { +static sp_err_t mock_dir_it_open(sp_sys_fd_t fd, sp_sys_dir_it_t* out) { return (sp_err_t)69; } -static sp_err_t mock_dir_read(sp_sys_dir_t* dir, sp_mem_buffer_t* buf) { +static sp_err_t mock_dir_it_read(sp_sys_dir_it_t* it, sp_mem_buffer_t* buf) { return (sp_err_t)69; } -static sp_err_t mock_dir_parse(sp_sys_dir_t* dir, sp_mem_buffer_t* buf, u64* cursor, sp_sys_dir_entry_t* out) { +static sp_err_t mock_dir_it_parse(sp_sys_dir_it_t* it, sp_mem_buffer_t* buf, u64* cursor, sp_sys_dir_entry_t* out) { return (sp_err_t)69; } -static sp_err_t mock_dir_close(sp_sys_dir_t* dir) { +static sp_err_t mock_dir_it_close(sp_sys_dir_it_t* it) { return (sp_err_t)69; } @@ -368,10 +368,10 @@ static const sp_sys_vtable_t mock = { .env = mock_env, .lseek = mock_lseek, .chdir = mock_chdir, - .dir_from_fd = mock_dir_from_fd, - .dir_read = mock_dir_read, - .dir_parse = mock_dir_parse, - .dir_close = mock_dir_close, + .dir_it_open = mock_dir_it_open, + .dir_it_read = mock_dir_it_read, + .dir_it_parse = mock_dir_it_parse, + .dir_it_close = mock_dir_it_close, }; static s64 call_read(void) { @@ -666,20 +666,20 @@ static s64 call_chdir(void) { return (s64)sp_sys_chdir(SP_NULLPTR, 0); } -static s64 call_dir_from_fd(void) { - return (s64)sp_sys_dir_from_fd(0, SP_NULLPTR); +static s64 call_dir_it_open(void) { + return (s64)sp_sys_dir_it_open(0, SP_NULLPTR); } -static s64 call_dir_read(void) { - return (s64)sp_sys_dir_read(SP_NULLPTR, SP_NULLPTR); +static s64 call_dir_it_read(void) { + return (s64)sp_sys_dir_it_read(SP_NULLPTR, SP_NULLPTR); } -static s64 call_dir_parse(void) { - return (s64)sp_sys_dir_parse(SP_NULLPTR, SP_NULLPTR, SP_NULLPTR, SP_NULLPTR); +static s64 call_dir_it_parse(void) { + return (s64)sp_sys_dir_it_parse(SP_NULLPTR, SP_NULLPTR, SP_NULLPTR, SP_NULLPTR); } -static s64 call_dir_close(void) { - return (s64)sp_sys_dir_close(SP_NULLPTR); +static s64 call_dir_it_close(void) { + return (s64)sp_sys_dir_it_close(SP_NULLPTR); } static const test_t tests [] = { @@ -751,10 +751,10 @@ static const test_t tests [] = { { "env", call_env, 69 }, { "lseek", call_lseek, 69 }, { "chdir", call_chdir, 69 }, - { "dir_from_fd", call_dir_from_fd, 69 }, - { "dir_read", call_dir_read, 69 }, - { "dir_parse", call_dir_parse, 69 }, - { "dir_close", call_dir_close, 69 }, + { "dir_it_open", call_dir_it_open, 69 }, + { "dir_it_read", call_dir_it_read, 69 }, + { "dir_it_parse", call_dir_it_parse, 69 }, + { "dir_it_close", call_dir_it_close, 69 }, }; static sp_err_t run(sp_test_t* t, test_t* c) { From 075c5e8c405c159074ba5f39be2168f5f98953db Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Tue, 22 Sep 2026 17:52:57 -0400 Subject: [PATCH 07/72] fs: trim copy tests --- test/fs/copy.c | 52 ++++++++------------------------------------------ 1 file changed, 8 insertions(+), 44 deletions(-) diff --git a/test/fs/copy.c b/test/fs/copy.c index 6d69f94c..1a495a5e 100644 --- a/test/fs/copy.c +++ b/test/fs/copy.c @@ -169,18 +169,6 @@ static const test_t tests [] = { { .path = "B", .exists = true, .kind = SP_FS_KIND_FILE, .content = "A" }, }, }, - { - .name = "file_source_dangling_symlink", - .setup = { - { .path = "L", .kind = FS_SETUP_SYMLINK, .target = "A" }, - }, - .src = "L", - .dst = "B", - .err = SP_ERR_SYS_NOT_FOUND, - .expect = { - { .path = "B" }, - }, - }, { .name = "file_source_missing", .setup = { @@ -397,32 +385,21 @@ static const test_t tests [] = { }, }, { - .name = "tree_into_itself", - .setup = { - { "A", FS_SETUP_DIR }, - { .path = "A/C", .content = "C" }, - }, - .op = OP_COPY_TREE, - .src = "A", - .dst = "A/B", - .expect = { - { .path = "A/B/C", .exists = true, .kind = SP_FS_KIND_FILE, .content = "C" }, - { .path = "A/B/B" }, - }, - }, - { - .name = "tree_into_itself_through_symlink", + .name = "tree_symlink_exclusive_existing", .setup = { { "A", FS_SETUP_DIR }, { .path = "A/C", .content = "C" }, - { .path = "L", .kind = FS_SETUP_SYMLINK, .target = "A" }, + { .path = "A/L", .kind = FS_SETUP_SYMLINK, .target = "A/C" }, + { "E", FS_SETUP_DIR }, + { .path = "E/L", .kind = FS_SETUP_SYMLINK, .target = "A" }, }, .op = OP_COPY_TREE, + .mode = SP_FS_ATOMIC_EXCLUSIVE, .src = "A", - .dst = "L/B", + .dst = "E", + .err = SP_ERR_SYS_EXISTS, .expect = { - { .path = "A/B/C", .exists = true, .kind = SP_FS_KIND_FILE, .content = "C" }, - { .path = "A/B/B" }, + { .path = "E/L", .exists = true, .kind = SP_FS_KIND_SYMLINK, .target = "A" }, }, }, { @@ -469,19 +446,6 @@ static const test_t tests [] = { { .path = "A/C", .exists = true, .kind = SP_FS_KIND_FILE, .content = "C" }, }, }, - { - .name = "tree_beside_itself", - .setup = { - { "A", FS_SETUP_DIR }, - { .path = "A/C", .content = "C" }, - }, - .op = OP_COPY_TREE, - .src = "A", - .dst = "A/../E", - .expect = { - { .path = "E/C", .exists = true, .kind = SP_FS_KIND_FILE, .content = "C" }, - }, - }, { .name = "tree_dest_parent_is_file", .setup = { From 42f45b77e8c5740883cd69dbdca45f1835402ef2 Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Tue, 22 Sep 2026 17:52:57 -0400 Subject: [PATCH 08/72] fs: trim canonicalize tests --- test/fs/canonicalize.c | 45 +----------------------------------------- 1 file changed, 1 insertion(+), 44 deletions(-) diff --git a/test/fs/canonicalize.c b/test/fs/canonicalize.c index f446d498..cb204941 100644 --- a/test/fs/canonicalize.c +++ b/test/fs/canonicalize.c @@ -95,13 +95,6 @@ static const test_t tests [] = { .empty = true, }, }, - { - .name = "nonexistent_relative", - .input = "no_such_file.txt", - .expect = { - .empty = true, - }, - }, { .name = "nonexistent_with_dotdot", .input = "no_such_dir/../also_missing.txt", @@ -109,18 +102,6 @@ static const test_t tests [] = { .empty = true, }, }, - { - .name = "result_is_normalized", - .setup = { - { "A", FS_SETUP_DIR }, - }, - .input = "A", - .expect = { - .no_trailing_slash = true, - .no_backslash = true, - .exists = true, - }, - }, { .name = "resolves_symlink_to_file", .setup = { @@ -132,6 +113,7 @@ static const test_t tests [] = { .no_backslash = true, .name = "A", .exists = true, + .same_as = "A", }, }, { @@ -161,20 +143,6 @@ static const test_t tests [] = { .exists = true, }, }, - { - .name = "symlink_with_dotdot", - .setup = { - { "A" }, - { "B", FS_SETUP_DIR }, - { .path = "B/L", .kind = FS_SETUP_SYMLINK, .target = "A" }, - }, - .input = "B/L", - .expect = { - .no_backslash = true, - .name = "A", - .exists = true, - }, - }, { .name = "idempotent", .setup = { @@ -186,17 +154,6 @@ static const test_t tests [] = { .idempotent = true, }, }, - { - .name = "through_symlink", - .setup = { - { "A" }, - { .path = "L", .kind = FS_SETUP_SYMLINK, .target = "A" }, - }, - .input = "L", - .expect = { - .same_as = "A", - }, - }, }; sp_test_each(fs, canonicalize, test_t, tests) { From 38c367cf59b17afba382e9546cf74222fd1c75e4 Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Tue, 22 Sep 2026 17:52:58 -0400 Subject: [PATCH 09/72] fs: test write_atomic --- test/fs/write_atomic.c | 67 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 test/fs/write_atomic.c diff --git a/test/fs/write_atomic.c b/test/fs/write_atomic.c new file mode 100644 index 00000000..f1e8d45e --- /dev/null +++ b/test/fs/write_atomic.c @@ -0,0 +1,67 @@ +#include "fs.h" + +typedef struct { + sp_err_t err; + fs_expected_path_t paths [FS_MAX_PATHS]; +} expect_t; + +typedef struct { + const c8* name; + fs_setup_t setup [FS_MAX_SETUP]; + const c8* path; + const c8* content; + expect_t expect; +} test_t; + +static const test_t tests [] = { + { + .name = "new", + .path = "A", + .content = "A", + .expect = { + .paths = { + { .path = "A", .exists = true, .kind = SP_FS_KIND_FILE, .content = "A" }, + }, + }, + }, + { + .name = "empty", + .path = "A", + .content = "", + .expect = { + .paths = { + { .path = "A", .exists = true, .kind = SP_FS_KIND_FILE, .content = "" }, + }, + }, + }, + { + .name = "replace_existing", + .setup = { + { .path = "A", .content = "old" }, + }, + .path = "A", + .content = "new", + .expect = { + .paths = { + { .path = "A", .exists = true, .kind = SP_FS_KIND_FILE, .content = "new" }, + }, + }, + }, +}; + +sp_test_each(fs, write_atomic, test_t, tests) { + sp_str_t sandbox = sp_test_dir(t); + fs_apply_setup(t, sandbox, it->setup); + + sp_sys_fd_t dir = SP_SYS_INVALID_FD; + sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, &dir)); + + sp_path_t path = { .dir = dir, .sub = sp_str_view(it->path) }; + sp_err_t err = sp_fs_write_atomic_at(path, sp_str_view(it->content)); + sp_sys_close(dir); + + sp_expect_err_eq(t, err, it->expect.err); + fs_expect_paths(t, sandbox, it->expect.paths); + fs_expect_no_temps(t, sandbox); + return SP_OK; +} From 543a1137e9e123871bf00530254cf179059c09a5 Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Tue, 22 Sep 2026 17:53:40 -0400 Subject: [PATCH 10/72] sys: delete iter and vtable tests --- spn.toml | 8 +- test/sys/iter.c | 143 ---- test/sys/vtable.c | 771 ---------------------- tools/windows/sp/spn/amalgamation.vcxproj | 2 +- tools/windows/sp/spn/sys.vcxproj | 2 +- 5 files changed, 6 insertions(+), 920 deletions(-) delete mode 100644 test/sys/iter.c delete mode 100644 test/sys/vtable.c diff --git a/spn.toml b/spn.toml index c5d6094f..706432f0 100644 --- a/spn.toml +++ b/spn.toml @@ -15,11 +15,11 @@ source = [ "test/main.c", "test/test/axis.c", "test/test/filter.c", "test/test/matrix.c", "test/test/resolve.c", "test/test/sweep.c", "test/test/tracking.c", - "test/sys/sys.c", "test/sys/open.c", "test/sys/path.c", "test/sys/iter.c", "test/sys/futex.c", + "test/sys/sys.c", "test/sys/open.c", "test/sys/path.c", "test/sys/futex.c", "test/sys/fd.c", "test/sys/nanosleep.c", "test/sys/timestamps.c", "test/sys/perms.c", "test/sys/self_path.c", "test/sys/pipe/data.c", "test/sys/pipe/blocking.c", "test/sys/transfer.c", "test/sys/readlink.c", "test/sys/socket/data.c", "test/sys/socket/listen.c", "test/sys/socket/desc.c", - "test/sys/tty/contract.c", "test/sys/tty/mode.c", "test/sys/vtable.c", + "test/sys/tty/contract.c", "test/sys/tty/mode.c", "test/sys/wait/event.c", "test/sys/wait/wait.c", "test/sys/inherit.c", "test/fs/dir/*.c", "test/fs/it/*.c", "test/fs/wtf8/*.c", @@ -39,11 +39,11 @@ source = ["test/test/*.c"] name = "sys" source = [ "test/main.c", - "test/sys/sys.c", "test/sys/open.c", "test/sys/path.c", "test/sys/iter.c", "test/sys/futex.c", + "test/sys/sys.c", "test/sys/open.c", "test/sys/path.c", "test/sys/futex.c", "test/sys/fd.c", "test/sys/nanosleep.c", "test/sys/timestamps.c", "test/sys/perms.c", "test/sys/self_path.c", "test/sys/pipe/data.c", "test/sys/pipe/blocking.c", "test/sys/transfer.c", "test/sys/readlink.c", "test/sys/socket/data.c", "test/sys/socket/listen.c", "test/sys/socket/desc.c", - "test/sys/tty/contract.c", "test/sys/tty/mode.c", "test/sys/vtable.c", + "test/sys/tty/contract.c", "test/sys/tty/mode.c", "test/sys/wait/event.c", "test/sys/wait/wait.c", "test/sys/inherit.c" ] diff --git a/test/sys/iter.c b/test/sys/iter.c deleted file mode 100644 index e0635432..00000000 --- a/test/sys/iter.c +++ /dev/null @@ -1,143 +0,0 @@ -#include "harness.h" - -#if !defined(SP_WASM) - -#define SYS_ITER_MAX_RAW 8 -#define SYS_ITER_BUF_SIZE 4096 - -typedef struct { - const c8* name; - sp_fs_kind_t kind; -} sys_iter_raw_t; - -typedef struct { - const c8* name; - sys_iter_raw_t raw [SYS_ITER_MAX_RAW]; -} sys_iter_case_t; - -#if defined(SP_WIN32) -static u64 sys_iter_pack(u8* buf, const sys_iter_raw_t* raw) { - u64 cursor = 0; - u64 len = 0; - sp_for(it, SYS_ITER_MAX_RAW) { - if (!raw[it].name) break; - u32 name_len = sp_cstr_len(raw[it].name); - - sp_nt_file_directory_information_t* d = sp_ptr_cast(sp_nt_file_directory_information_t*, buf + cursor); - *d = sp_zero_s(sp_nt_file_directory_information_t); - d->FileNameLength = name_len * sizeof(u16); - sp_for(n, name_len) { - d->FileName[n] = (u16)raw[it].name[n]; - } - switch (raw[it].kind) { - case SP_FS_KIND_FILE: { d->FileAttributes = FILE_ATTRIBUTE_NORMAL; break; } - case SP_FS_KIND_DIR: { d->FileAttributes = FILE_ATTRIBUTE_DIRECTORY; break; } - case SP_FS_KIND_SYMLINK: { d->FileAttributes = FILE_ATTRIBUTE_REPARSE_POINT; break; } - case SP_FS_KIND_NONE: { break; } - } - - len = cursor + sizeof(sp_nt_file_directory_information_t) + d->FileNameLength; - bool last = it + 1 >= SYS_ITER_MAX_RAW || !raw[it + 1].name; - d->NextEntryOffset = last ? 0 : (u32)sp_align_offset(sizeof(sp_nt_file_directory_information_t) + d->FileNameLength, 8); - cursor += d->NextEntryOffset; - } - return len; -} -#elif defined(SP_LINUX) -static u64 sys_iter_pack(u8* buf, const sys_iter_raw_t* raw) { - u64 cursor = 0; - sp_for(it, SYS_ITER_MAX_RAW) { - if (!raw[it].name) break; - u32 name_len = sp_cstr_len(raw[it].name); - - sp_sys_dirent64_t* d = sp_ptr_cast(sp_sys_dirent64_t*, buf + cursor); - d->d_ino = it + 1; - d->d_off = 0; - d->d_reclen = (u16)sp_align_offset(sizeof(sp_sys_dirent64_t) + name_len + 1, 8); - switch (raw[it].kind) { - case SP_FS_KIND_FILE: { d->d_type = SP_DT_REG; break; } - case SP_FS_KIND_DIR: { d->d_type = SP_DT_DIR; break; } - case SP_FS_KIND_SYMLINK: { d->d_type = SP_DT_LNK; break; } - case SP_FS_KIND_NONE: { d->d_type = 0; break; } - } - sp_mem_copy(d->d_name, raw[it].name, name_len + 1); - cursor += d->d_reclen; - } - return cursor; -} -#elif defined(SP_MACOS) || defined(SP_COSMO) -static u64 sys_iter_pack(u8* buf, const sys_iter_raw_t* raw) { - u64 cursor = 0; - sp_for(it, SYS_ITER_MAX_RAW) { - if (!raw[it].name) break; - u32 name_len = sp_cstr_len(raw[it].name); - - struct dirent d = sp_zero; - d.d_reclen = sizeof(struct dirent); - switch (raw[it].kind) { - case SP_FS_KIND_FILE: { d.d_type = SP_DT_REG; break; } - case SP_FS_KIND_DIR: { d.d_type = SP_DT_DIR; break; } - case SP_FS_KIND_SYMLINK: { d.d_type = SP_DT_LNK; break; } - case SP_FS_KIND_NONE: { d.d_type = 0; break; } - } - sp_mem_copy(d.d_name, raw[it].name, name_len + 1); - sp_mem_copy(buf + cursor, &d, sizeof(struct dirent)); - cursor += sizeof(struct dirent); - } - return cursor; -} -#endif - -static const sys_iter_case_t sys_iter_cases [] = { - { - .name = "decodes_kinds", - .raw = { - { "A", SP_FS_KIND_FILE }, - { "B", SP_FS_KIND_DIR }, - { "L", SP_FS_KIND_SYMLINK }, - }, - }, - { - .name = "returns_dot_entries", - .raw = { - { ".", SP_FS_KIND_DIR }, - { "..", SP_FS_KIND_DIR }, - { "A", SP_FS_KIND_FILE }, - }, - }, -}; - -sp_test_each(sys, iter, sys_iter_case_t, sys_iter_cases) { - SP_ALIGNED u8 raw [SYS_ITER_BUF_SIZE]; - sp_mem_buffer_t buf = { - .data = raw, - .capacity = sizeof(raw), - }; - buf.len = sys_iter_pack(raw, it->raw); - - sp_sys_dir_it_t dir = sp_zero; - u64 cursor = 0; - - sp_carr_for(it->raw, n) { - if (!it->raw[n].name) break; - - if (cursor >= buf.len) { - sp_test_fail(t, "parse consumed the buffer before {}", sp_fmt_cstr(it->raw[n].name)); - return SP_ERR; - } - - sp_test_kv_c(t, "entry", it->raw[n].name); - - sp_sys_dir_entry_t entry = sp_zero; - sp_must_ok(t, sp_sys_dir_it_parse(&dir, &buf, &cursor, &entry)); - - sp_expect_str_eq(t, sp_str(entry.name, entry.len), sp_cstr_as_str(it->raw[n].name)); - sp_expect_eq(t, (u32)entry.kind, (u32)it->raw[n].kind); - } - - sp_test_kv_clear(t, "entry"); - sp_expect_eq(t, cursor, buf.len); - return SP_OK; -} - -#endif diff --git a/test/sys/vtable.c b/test/sys/vtable.c deleted file mode 100644 index 5eedb80d..00000000 --- a/test/sys/vtable.c +++ /dev/null @@ -1,771 +0,0 @@ -#include "sp.h" -#include "sp/sp_test.h" - -typedef struct { - const c8* name; - s64 (*call)(void); - s64 expect; -} test_t; - -static s64 recorded; - -static sp_err_t mock_read(sp_sys_fd_t fd, void* buf, u64 count, u64* bytes_read) { - return (sp_err_t)69; -} - -static sp_err_t mock_write(sp_sys_fd_t fd, const void* buf, u64 count, u64* bytes_written) { - return (sp_err_t)69; -} - -static sp_err_t mock_pread(sp_sys_fd_t fd, void* buf, u64 count, u64 offset, u64* bytes_read) { - return (sp_err_t)69; -} - -static sp_err_t mock_pwrite(sp_sys_fd_t fd, const void* buf, u64 count, u64 offset, u64* bytes_written) { - return (sp_err_t)69; -} - -static sp_err_t mock_transfer(sp_sys_fd_t in, u64* in_pos, sp_sys_fd_t out, u64 count, u64* bytes_moved) { - return (sp_err_t)69; -} - -static sp_err_t mock_transfer_positional(sp_sys_fd_t in, u64* in_pos, sp_sys_fd_t out, u64 count, u64 offset, u64* bytes_moved) { - return (sp_err_t)69; -} - -static sp_sys_fd_t mock_get_root(s32 it) { - return 69; -} - -static s64 mock_get_exe_path(c8* buf, u64 size) { - return 69; -} - -static s64 mock_get_cwd_path(c8* buf, u64 size) { - return 69; -} - -static s64 mock_get_storage_path(c8* buf, u64 size) { - return 69; -} - -static s64 mock_get_config_path(c8* buf, u64 size) { - return 69; -} - -static s64 mock_get_fd_path(sp_sys_fd_t fd, c8* buf, u64 size) { - return 69; -} - -static sp_err_t mock_open(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_open_mode_t mode, u32 flags, sp_sys_fd_t* out) { - *out = 69; - return (sp_err_t)69; -} - -static sp_err_t mock_open_dir(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_fd_t* out) { - *out = 69; - return (sp_err_t)69; -} - -static sp_err_t mock_close(sp_sys_fd_t fd) { - return (sp_err_t)69; -} - -static sp_err_t mock_pipe(sp_sys_pipe_t* pipe, sp_sys_pipe_desc_t desc) { - return (sp_err_t)69; -} - -static sp_err_t mock_pipe_ready(sp_sys_fd_t fd, u8* ready) { - return (sp_err_t)69; -} - -static sp_err_t mock_mkdir(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_file_perms_t perms) { - return (sp_err_t)69; -} - -static sp_err_t mock_rmdir(sp_sys_fd_t fd, const c8* path, u32 len) { - return (sp_err_t)69; -} - -static sp_err_t mock_unlink(sp_sys_fd_t fd, const c8* path, u32 len) { - return (sp_err_t)69; -} - -static sp_err_t mock_rename(sp_sys_fd_t from_fd, const c8* from, u32 from_len, sp_sys_fd_t to_fd, const c8* to, u32 to_len) { - return (sp_err_t)69; -} - -static sp_err_t mock_link(sp_sys_fd_t from_fd, const c8* existing, u32 existing_len, sp_sys_fd_t to_fd, const c8* alias, u32 alias_len) { - return (sp_err_t)69; -} - -static sp_err_t mock_symlink(const c8* existing, u32 existing_len, sp_sys_fd_t to_fd, const c8* alias, u32 alias_len) { - return (sp_err_t)69; -} - -static sp_err_t mock_readlink(sp_sys_fd_t fd, const c8* path, u32 len, c8* buf, u64 size, u64* target_len) { - return (sp_err_t)69; -} - -static sp_err_t mock_get_path_metadata(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_file_meta_t* st) { - return (sp_err_t)69; -} - -static sp_err_t mock_get_link_metadata(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_file_meta_t* st) { - return (sp_err_t)69; -} - -static sp_err_t mock_get_file_metadata(sp_sys_fd_t fd, sp_sys_file_meta_t* st) { - return (sp_err_t)69; -} - -static sp_err_t mock_set_file_perms(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_file_perms_t perms) { - return (sp_err_t)69; -} - -static sp_err_t mock_clock_gettime(s32 clockid, sp_sys_timespec_t* ts) { - return (sp_err_t)69; -} - -static sp_err_t mock_nanosleep(const sp_sys_timespec_t* req, sp_sys_timespec_t* rem) { - return (sp_err_t)69; -} - -static bool mock_futex_wait(u32* addr, u32 expected, const sp_sys_timespec_t* timeout) { - return true; -} - -static void mock_futex_wake(u32* addr) { - recorded = 69; -} - -static void mock_futex_wake_all(u32* addr) { - recorded = 69; -} - -static sp_err_t mock_event_open(sp_sys_event_t* out) { - return (sp_err_t)69; -} - -static sp_err_t mock_event_signal(sp_sys_event_t event) { - return (sp_err_t)69; -} - -static sp_err_t mock_event_clear(sp_sys_event_t event) { - return (sp_err_t)69; -} - -static sp_err_t mock_wait(const sp_sys_fd_t* fds, u64 n, u32 timeout_ms, u64* signaled) { - return (sp_err_t)69; -} - -static sp_err_t mock_tty_get(sp_sys_fd_t fd, sp_sys_tty_attr_t* attr) { - return (sp_err_t)69; -} - -static sp_err_t mock_tty_set(sp_sys_fd_t fd, const sp_sys_tty_attr_t* attr) { - return (sp_err_t)69; -} - -static sp_err_t mock_tty_size(sp_sys_fd_t fd, u32* cols, u32* rows) { - return (sp_err_t)69; -} - -static bool mock_is_tty(sp_sys_fd_t fd) { - return true; -} - -static sp_err_t mock_tty_ready(sp_sys_fd_t fd, u8* ready) { - return (sp_err_t)69; -} - -static sp_err_t mock_tty_mode_apply(sp_sys_tty_attr_t* in, sp_sys_tty_attr_t* out, sp_sys_tty_mode_t mode) { - return (sp_err_t)69; -} - -static sp_err_t mock_tty_use_vt(sp_sys_fd_t fd) { - return (sp_err_t)69; -} - -static sp_err_t mock_socket_open(sp_sys_socket_t* out, sp_sys_handle_desc_t desc) { - return (sp_err_t)69; -} - -static sp_err_t mock_socket_bind(sp_sys_socket_t socket, sp_sys_ipv4_t addr) { - return (sp_err_t)69; -} - -static sp_err_t mock_socket_listen(sp_sys_socket_t socket, u32 backlog) { - return (sp_err_t)69; -} - -static sp_err_t mock_socket_connect(sp_sys_socket_t socket, sp_sys_ipv4_t addr) { - return (sp_err_t)69; -} - -static sp_err_t mock_socket_error(sp_sys_socket_t socket) { - return (sp_err_t)69; -} - -static sp_err_t mock_socket_accept(sp_sys_socket_t listener, sp_sys_handle_desc_t desc, sp_sys_socket_t* out) { - return (sp_err_t)69; -} - -static sp_err_t mock_socket_close(sp_sys_socket_t socket) { - return (sp_err_t)69; -} - -static sp_err_t mock_socket_recv(sp_sys_socket_t socket, void* buf, u64 count, u64* bytes_read) { - return (sp_err_t)69; -} - -static sp_err_t mock_socket_send(sp_sys_socket_t socket, const void* buf, u64 count, u64* bytes_written) { - return (sp_err_t)69; -} - -static sp_err_t mock_socket_wait(sp_sys_socket_t socket, bool readable, u32 timeout_ms) { - return (sp_err_t)69; -} - -static sp_err_t mock_socket_set_nonblocking(sp_sys_socket_t socket) { - return (sp_err_t)69; -} - -static sp_err_t mock_socket_reuse_addr(sp_sys_socket_t socket) { - return (sp_err_t)69; -} - -static sp_err_t mock_socket_local_port(sp_sys_socket_t socket, u16* out) { - return (sp_err_t)69; -} - -static void* mock_alloc(u64 size) { - return (void*)(uintptr_t)69; -} - -static void mock_free(void* ptr, u64 size) { - recorded = 69; -} - -static void* mock_memcpy(void* dest, const void* src, u64 n) { - return (void*)(uintptr_t)69; -} - -static void* mock_memmove(void* dest, const void* src, u64 n) { - return (void*)(uintptr_t)69; -} - -static void* mock_memset(void* dest, u8 fill, u64 n) { - return (void*)(uintptr_t)69; -} - -static s32 mock_memcmp(const void* a, const void* b, u64 n) { - return 69; -} - -static void mock_assert(bool cond) { - recorded = 69; -} - -static void mock_exit(s32 code) { - recorded = 69; -} - -static void mock_env(const c8** env, u32* len) { - recorded = 69; -} - -static s64 mock_lseek(sp_sys_fd_t fd, s64 offset, s32 whence) { - return 69; -} - -static sp_err_t mock_chdir(const c8* path, u32 len) { - return (sp_err_t)69; -} - -static sp_err_t mock_dir_it_open(sp_sys_fd_t fd, sp_sys_dir_it_t* out) { - return (sp_err_t)69; -} - -static sp_err_t mock_dir_it_read(sp_sys_dir_it_t* it, sp_mem_buffer_t* buf) { - return (sp_err_t)69; -} - -static sp_err_t mock_dir_it_parse(sp_sys_dir_it_t* it, sp_mem_buffer_t* buf, u64* cursor, sp_sys_dir_entry_t* out) { - return (sp_err_t)69; -} - -static sp_err_t mock_dir_it_close(sp_sys_dir_it_t* it) { - return (sp_err_t)69; -} - -static const sp_sys_vtable_t mock = { - .read = mock_read, - .write = mock_write, - .pread = mock_pread, - .pwrite = mock_pwrite, - .transfer = mock_transfer, - .transfer_positional = mock_transfer_positional, - .get_root = mock_get_root, - .get_exe_path = mock_get_exe_path, - .get_cwd_path = mock_get_cwd_path, - .get_storage_path = mock_get_storage_path, - .get_config_path = mock_get_config_path, - .get_fd_path = mock_get_fd_path, - .open = mock_open, - .open_dir = mock_open_dir, - .close = mock_close, - .pipe = mock_pipe, - .pipe_ready = mock_pipe_ready, - .mkdir = mock_mkdir, - .rmdir = mock_rmdir, - .unlink = mock_unlink, - .rename = mock_rename, - .link = mock_link, - .symlink = mock_symlink, - .readlink = mock_readlink, - .get_path_metadata = mock_get_path_metadata, - .get_link_metadata = mock_get_link_metadata, - .get_file_metadata = mock_get_file_metadata, - .set_file_perms = mock_set_file_perms, - .clock_gettime = mock_clock_gettime, - .nanosleep = mock_nanosleep, - .futex_wait = mock_futex_wait, - .futex_wake = mock_futex_wake, - .futex_wake_all = mock_futex_wake_all, - .event_open = mock_event_open, - .event_signal = mock_event_signal, - .event_clear = mock_event_clear, - .wait = mock_wait, - .tty_get = mock_tty_get, - .tty_set = mock_tty_set, - .tty_size = mock_tty_size, - .is_tty = mock_is_tty, - .tty_ready = mock_tty_ready, - .tty_mode_apply = mock_tty_mode_apply, - .tty_use_vt = mock_tty_use_vt, - .socket_open = mock_socket_open, - .socket_bind = mock_socket_bind, - .socket_listen = mock_socket_listen, - .socket_connect = mock_socket_connect, - .socket_error = mock_socket_error, - .socket_accept = mock_socket_accept, - .socket_close = mock_socket_close, - .socket_recv = mock_socket_recv, - .socket_send = mock_socket_send, - .socket_wait = mock_socket_wait, - .socket_set_nonblocking = mock_socket_set_nonblocking, - .socket_reuse_addr = mock_socket_reuse_addr, - .socket_local_port = mock_socket_local_port, - .alloc = mock_alloc, - .free = mock_free, - .memcpy = mock_memcpy, - .memmove = mock_memmove, - .memset = mock_memset, - .memcmp = mock_memcmp, - .assert = mock_assert, - .exit = mock_exit, - .env = mock_env, - .lseek = mock_lseek, - .chdir = mock_chdir, - .dir_it_open = mock_dir_it_open, - .dir_it_read = mock_dir_it_read, - .dir_it_parse = mock_dir_it_parse, - .dir_it_close = mock_dir_it_close, -}; - -static s64 call_read(void) { - return (s64)sp_sys_read(0, SP_NULLPTR, 0, SP_NULLPTR); -} - -static s64 call_write(void) { - return (s64)sp_sys_write(0, SP_NULLPTR, 0, SP_NULLPTR); -} - -static s64 call_pread(void) { - return (s64)sp_sys_pread(0, SP_NULLPTR, 0, 0, SP_NULLPTR); -} - -static s64 call_pwrite(void) { - return (s64)sp_sys_pwrite(0, SP_NULLPTR, 0, 0, SP_NULLPTR); -} - -static s64 call_transfer(void) { - return (s64)sp_sys_transfer(0, SP_NULLPTR, 0, 0, SP_NULLPTR); -} - -static s64 call_transfer_positional(void) { - return (s64)sp_sys_transfer_positional(0, SP_NULLPTR, 0, 0, 0, SP_NULLPTR); -} - -static s64 call_get_root(void) { - return (s64)sp_sys_get_root(0); -} - -static s64 call_get_exe_path(void) { - return sp_sys_get_exe_path(SP_NULLPTR, 0); -} - -static s64 call_get_cwd_path(void) { - return sp_sys_get_cwd_path(SP_NULLPTR, 0); -} - -static s64 call_get_storage_path(void) { - return sp_sys_get_storage_path(SP_NULLPTR, 0); -} - -static s64 call_get_config_path(void) { - return sp_sys_get_config_path(SP_NULLPTR, 0); -} - -static s64 call_get_fd_path(void) { - return sp_sys_get_fd_path(0, SP_NULLPTR, 0); -} - -static s64 call_open(void) { - sp_sys_fd_t out = SP_SYS_INVALID_FD; - sp_sys_open(0, SP_NULLPTR, 0, SP_SYS_OPEN_MODE_RO, 0, &out); - return (s64)out; -} - -static s64 call_open_dir(void) { - sp_sys_fd_t out = SP_SYS_INVALID_FD; - sp_sys_open_dir(0, SP_NULLPTR, 0, &out); - return (s64)out; -} - -static s64 call_close(void) { - return (s64)sp_sys_close(0); -} - -static s64 call_pipe(void) { - return (s64)sp_sys_pipe(SP_NULLPTR, sp_zero_s(sp_sys_pipe_desc_t)); -} - -static s64 call_pipe_ready(void) { - u8 ready = 0; - return (s64)sp_sys_pipe_ready(0, &ready); -} - -static s64 call_mkdir(void) { - return (s64)sp_sys_mkdir(0, SP_NULLPTR, 0, sp_zero_s(sp_sys_file_perms_t)); -} - -static s64 call_rmdir(void) { - return (s64)sp_sys_rmdir(0, SP_NULLPTR, 0); -} - -static s64 call_unlink(void) { - return (s64)sp_sys_unlink(0, SP_NULLPTR, 0); -} - -static s64 call_rename(void) { - return (s64)sp_sys_rename(0, SP_NULLPTR, 0, 0, SP_NULLPTR, 0); -} - -static s64 call_link(void) { - return (s64)sp_sys_link(0, SP_NULLPTR, 0, 0, SP_NULLPTR, 0); -} - -static s64 call_symlink(void) { - return (s64)sp_sys_symlink(SP_NULLPTR, 0, 0, SP_NULLPTR, 0); -} - -static s64 call_readlink(void) { - u64 len = 0; - return (s64)sp_sys_readlink(0, SP_NULLPTR, 0, SP_NULLPTR, 0, &len); -} - -static s64 call_get_path_metadata(void) { - return (s64)sp_sys_get_path_metadata(0, SP_NULLPTR, 0, SP_NULLPTR); -} - -static s64 call_get_link_metadata(void) { - return (s64)sp_sys_get_link_metadata(0, SP_NULLPTR, 0, SP_NULLPTR); -} - -static s64 call_get_file_metadata(void) { - return (s64)sp_sys_get_file_metadata(0, SP_NULLPTR); -} - -static s64 call_set_file_perms(void) { - return (s64)sp_sys_set_file_perms(0, SP_NULLPTR, 0, sp_zero_s(sp_sys_file_perms_t)); -} - -static s64 call_clock_gettime(void) { - return (s64)sp_sys_clock_gettime(0, SP_NULLPTR); -} - -static s64 call_nanosleep(void) { - return (s64)sp_sys_nanosleep(SP_NULLPTR, SP_NULLPTR); -} - -static s64 call_futex_wait(void) { - return (s64)sp_sys_futex_wait(SP_NULLPTR, 0, SP_NULLPTR); -} - -static s64 call_futex_wake(void) { - recorded = 0; - sp_sys_futex_wake(SP_NULLPTR); - return recorded; -} - -static s64 call_futex_wake_all(void) { - recorded = 0; - sp_sys_futex_wake_all(SP_NULLPTR); - return recorded; -} - -static s64 call_event_open(void) { - return (s64)sp_sys_event_open(SP_NULLPTR); -} - -static s64 call_event_signal(void) { - return (s64)sp_sys_event_signal(sp_zero_s(sp_sys_event_t)); -} - -static s64 call_event_clear(void) { - return (s64)sp_sys_event_clear(sp_zero_s(sp_sys_event_t)); -} - -static s64 call_wait(void) { - sp_sys_fd_t fds [1] = sp_zero; - return (s64)sp_sys_wait(fds, 0, 0, SP_NULLPTR); -} - -static s64 call_tty_get(void) { - return (s64)sp_sys_tty_get(0, SP_NULLPTR); -} - -static s64 call_tty_set(void) { - return (s64)sp_sys_tty_set(0, SP_NULLPTR); -} - -static s64 call_tty_size(void) { - return (s64)sp_sys_tty_size(0, SP_NULLPTR, SP_NULLPTR); -} - -static s64 call_is_tty(void) { - return (s64)sp_sys_is_tty(0); -} - -static s64 call_tty_ready(void) { - u8 ready = 0; - return (s64)sp_sys_tty_ready(0, &ready); -} - -static s64 call_tty_mode_apply(void) { - return (s64)sp_sys_tty_mode_apply(SP_NULLPTR, SP_NULLPTR, SP_SYS_TTY_MODE_RAW); -} - -static s64 call_tty_use_vt(void) { - return (s64)sp_sys_tty_use_vt(0); -} - -static s64 call_socket_open(void) { - return (s64)sp_sys_socket_open(SP_NULLPTR, sp_zero_s(sp_sys_handle_desc_t)); -} - -static s64 call_socket_bind(void) { - return (s64)sp_sys_socket_bind(0, sp_zero_s(sp_sys_ipv4_t)); -} - -static s64 call_socket_listen(void) { - return (s64)sp_sys_socket_listen(0, 0); -} - -static s64 call_socket_connect(void) { - return (s64)sp_sys_socket_connect(0, sp_zero_s(sp_sys_ipv4_t)); -} - -static s64 call_socket_error(void) { - return (s64)sp_sys_socket_error(0); -} - -static s64 call_socket_accept(void) { - return (s64)sp_sys_socket_accept(0, sp_zero_s(sp_sys_handle_desc_t), SP_NULLPTR); -} - -static s64 call_socket_close(void) { - return (s64)sp_sys_socket_close(0); -} - -static s64 call_socket_recv(void) { - return (s64)sp_sys_socket_recv(0, SP_NULLPTR, 0, SP_NULLPTR); -} - -static s64 call_socket_send(void) { - return (s64)sp_sys_socket_send(0, SP_NULLPTR, 0, SP_NULLPTR); -} - -static s64 call_socket_wait(void) { - return (s64)sp_sys_socket_wait(0, false, 0); -} - -static s64 call_socket_set_nonblocking(void) { - return (s64)sp_sys_socket_set_nonblocking(0); -} - -static s64 call_socket_reuse_addr(void) { - return (s64)sp_sys_socket_reuse_addr(0); -} - -static s64 call_socket_local_port(void) { - return (s64)sp_sys_socket_local_port(0, SP_NULLPTR); -} - -static s64 call_alloc(void) { - return (s64)(uintptr_t)sp_sys_alloc(0); -} - -static s64 call_free(void) { - recorded = 0; - sp_sys_free(SP_NULLPTR, 0); - return recorded; -} - -static s64 call_memcpy(void) { - return (s64)(uintptr_t)sp_sys_memcpy(SP_NULLPTR, SP_NULLPTR, 0); -} - -static s64 call_memmove(void) { - return (s64)(uintptr_t)sp_sys_memmove(SP_NULLPTR, SP_NULLPTR, 0); -} - -static s64 call_memset(void) { - return (s64)(uintptr_t)sp_sys_memset(SP_NULLPTR, 0, 0); -} - -static s64 call_memcmp(void) { - return (s64)sp_sys_memcmp(SP_NULLPTR, SP_NULLPTR, 0); -} - -static s64 call_assert(void) { - recorded = 0; - sp_sys_assert(true); - return recorded; -} - -static s64 call_exit(void) { - recorded = 0; - sp_sys_exit(0); - return recorded; -} - -static s64 call_env(void) { - recorded = 0; - sp_sys_env(SP_NULLPTR, SP_NULLPTR); - return recorded; -} - -static s64 call_lseek(void) { - return sp_sys_lseek(0, 0, 0); -} - -static s64 call_chdir(void) { - return (s64)sp_sys_chdir(SP_NULLPTR, 0); -} - -static s64 call_dir_it_open(void) { - return (s64)sp_sys_dir_it_open(0, SP_NULLPTR); -} - -static s64 call_dir_it_read(void) { - return (s64)sp_sys_dir_it_read(SP_NULLPTR, SP_NULLPTR); -} - -static s64 call_dir_it_parse(void) { - return (s64)sp_sys_dir_it_parse(SP_NULLPTR, SP_NULLPTR, SP_NULLPTR, SP_NULLPTR); -} - -static s64 call_dir_it_close(void) { - return (s64)sp_sys_dir_it_close(SP_NULLPTR); -} - -static const test_t tests [] = { - { "read", call_read, 69 }, - { "write", call_write, 69 }, - { "pread", call_pread, 69 }, - { "pwrite", call_pwrite, 69 }, - { "transfer", call_transfer, 69 }, - { "transfer_positional", call_transfer_positional, 69 }, - { "get_root", call_get_root, 69 }, - { "get_exe_path", call_get_exe_path, 69 }, - { "get_cwd_path", call_get_cwd_path, 69 }, - { "get_storage_path", call_get_storage_path, 69 }, - { "get_config_path", call_get_config_path, 69 }, - { "get_fd_path", call_get_fd_path, 69 }, - { "open", call_open, 69 }, - { "open_dir", call_open_dir, 69 }, - { "close", call_close, 69 }, - { "pipe", call_pipe, 69 }, - { "pipe_ready", call_pipe_ready, 69 }, - { "mkdir", call_mkdir, 69 }, - { "rmdir", call_rmdir, 69 }, - { "unlink", call_unlink, 69 }, - { "rename", call_rename, 69 }, - { "link", call_link, 69 }, - { "symlink", call_symlink, 69 }, - { "readlink", call_readlink, 69 }, - { "get_path_metadata", call_get_path_metadata, 69 }, - { "get_link_metadata", call_get_link_metadata, 69 }, - { "get_file_metadata", call_get_file_metadata, 69 }, - { "set_file_perms", call_set_file_perms, 69 }, - { "clock_gettime", call_clock_gettime, 69 }, - { "nanosleep", call_nanosleep, 69 }, - { "futex_wait", call_futex_wait, 1 }, - { "futex_wake", call_futex_wake, 69 }, - { "futex_wake_all", call_futex_wake_all, 69 }, - { "event_open", call_event_open, 69 }, - { "event_signal", call_event_signal, 69 }, - { "event_clear", call_event_clear, 69 }, - { "wait", call_wait, 69 }, - { "tty_get", call_tty_get, 69 }, - { "tty_set", call_tty_set, 69 }, - { "tty_size", call_tty_size, 69 }, - { "is_tty", call_is_tty, 1 }, - { "tty_ready", call_tty_ready, 69 }, - { "tty_mode_apply", call_tty_mode_apply, 69 }, - { "tty_use_vt", call_tty_use_vt, 69 }, - { "socket_open", call_socket_open, 69 }, - { "socket_bind", call_socket_bind, 69 }, - { "socket_listen", call_socket_listen, 69 }, - { "socket_connect", call_socket_connect, 69 }, - { "socket_error", call_socket_error, 69 }, - { "socket_accept", call_socket_accept, 69 }, - { "socket_close", call_socket_close, 69 }, - { "socket_recv", call_socket_recv, 69 }, - { "socket_send", call_socket_send, 69 }, - { "socket_wait", call_socket_wait, 69 }, - { "socket_set_nonblocking", call_socket_set_nonblocking, 69 }, - { "socket_reuse_addr", call_socket_reuse_addr, 69 }, - { "socket_local_port", call_socket_local_port, 69 }, - { "alloc", call_alloc, 69 }, - { "free", call_free, 69 }, - { "memcpy", call_memcpy, 69 }, - { "memmove", call_memmove, 69 }, - { "memset", call_memset, 69 }, - { "memcmp", call_memcmp, 69 }, - { "assert", call_assert, 69 }, - { "exit", call_exit, 69 }, - { "env", call_env, 69 }, - { "lseek", call_lseek, 69 }, - { "chdir", call_chdir, 69 }, - { "dir_it_open", call_dir_it_open, 69 }, - { "dir_it_read", call_dir_it_read, 69 }, - { "dir_it_parse", call_dir_it_parse, 69 }, - { "dir_it_close", call_dir_it_close, 69 }, -}; - -static sp_err_t run(sp_test_t* t, test_t* c) { - const sp_sys_vtable_t* old = sp_sys_set_vtable(&mock); - s64 got = c->call(); - const sp_sys_vtable_t* swapped = sp_sys_set_vtable(old); - - sp_expect(t, old == &sp_sys_vtable_platform); - sp_expect(t, swapped == &mock); - sp_expect_eq(t, got, c->expect); - return SP_OK; -} - -sp_test_each_fn(sys, vtable, test_t, tests, run, .serial = true); diff --git a/tools/windows/sp/spn/amalgamation.vcxproj b/tools/windows/sp/spn/amalgamation.vcxproj index 376ef584..4e850721 100644 --- a/tools/windows/sp/spn/amalgamation.vcxproj +++ b/tools/windows/sp/spn/amalgamation.vcxproj @@ -18,7 +18,7 @@ amalgamation 10.0 amalgamation - ..\..\..\..\test\main.c;..\..\..\..\test\test\axis.c;..\..\..\..\test\test\filter.c;..\..\..\..\test\test\matrix.c;..\..\..\..\test\test\resolve.c;..\..\..\..\test\test\sweep.c;..\..\..\..\test\test\tracking.c;..\..\..\..\test\sys\sys.c;..\..\..\..\test\sys\open.c;..\..\..\..\test\sys\path.c;..\..\..\..\test\sys\iter.c;..\..\..\..\test\sys\futex.c;..\..\..\..\test\sys\fd.c;..\..\..\..\test\sys\nanosleep.c;..\..\..\..\test\sys\timestamps.c;..\..\..\..\test\sys\perms.c;..\..\..\..\test\sys\self_path.c;..\..\..\..\test\sys\pipe\data.c;..\..\..\..\test\sys\pipe\blocking.c;..\..\..\..\test\sys\transfer.c;..\..\..\..\test\sys\readlink.c;..\..\..\..\test\sys\socket\data.c;..\..\..\..\test\sys\socket\listen.c;..\..\..\..\test\sys\socket\desc.c;..\..\..\..\test\sys\tty\contract.c;..\..\..\..\test\sys\tty\mode.c;..\..\..\..\test\sys\vtable.c;..\..\..\..\test\sys\wait\event.c;..\..\..\..\test\sys\wait\wait.c;..\..\..\..\test\sys\inherit.c;..\..\..\..\test\fs\dir\*.c;..\..\..\..\test\fs\it\*.c;..\..\..\..\test\fs\wtf8\*.c;..\..\..\..\test\fs\*.c;..\..\..\..\test\fs\windows\nt_path.c;..\..\..\..\test\msvc\*.c;..\..\..\..\test\mem\uninitialized.c;..\..\..\..\test\atomic.c;..\..\..\..\test\sync\*.c;..\..\..\..\test\sync\os\*.c + ..\..\..\..\test\main.c;..\..\..\..\test\test\axis.c;..\..\..\..\test\test\filter.c;..\..\..\..\test\test\matrix.c;..\..\..\..\test\test\resolve.c;..\..\..\..\test\test\sweep.c;..\..\..\..\test\test\tracking.c;..\..\..\..\test\sys\sys.c;..\..\..\..\test\sys\open.c;..\..\..\..\test\sys\path.c;..\..\..\..\test\sys\futex.c;..\..\..\..\test\sys\fd.c;..\..\..\..\test\sys\nanosleep.c;..\..\..\..\test\sys\timestamps.c;..\..\..\..\test\sys\perms.c;..\..\..\..\test\sys\self_path.c;..\..\..\..\test\sys\pipe\data.c;..\..\..\..\test\sys\pipe\blocking.c;..\..\..\..\test\sys\transfer.c;..\..\..\..\test\sys\readlink.c;..\..\..\..\test\sys\socket\data.c;..\..\..\..\test\sys\socket\listen.c;..\..\..\..\test\sys\socket\desc.c;..\..\..\..\test\sys\tty\contract.c;..\..\..\..\test\sys\tty\mode.c;..\..\..\..\test\sys\wait\event.c;..\..\..\..\test\sys\wait\wait.c;..\..\..\..\test\sys\inherit.c;..\..\..\..\test\fs\dir\*.c;..\..\..\..\test\fs\it\*.c;..\..\..\..\test\fs\wtf8\*.c;..\..\..\..\test\fs\*.c;..\..\..\..\test\fs\windows\nt_path.c;..\..\..\..\test\msvc\*.c;..\..\..\..\test\mem\uninitialized.c;..\..\..\..\test\atomic.c;..\..\..\..\test\sync\*.c;..\..\..\..\test\sync\os\*.c ..\..\..\..\sp.h;..\..\..\..\sp\sp_test.h false diff --git a/tools/windows/sp/spn/sys.vcxproj b/tools/windows/sp/spn/sys.vcxproj index 0485588b..3c7e40af 100644 --- a/tools/windows/sp/spn/sys.vcxproj +++ b/tools/windows/sp/spn/sys.vcxproj @@ -18,7 +18,7 @@ sys 10.0 sys - ..\..\..\..\test\main.c;..\..\..\..\test\sys\sys.c;..\..\..\..\test\sys\open.c;..\..\..\..\test\sys\path.c;..\..\..\..\test\sys\iter.c;..\..\..\..\test\sys\futex.c;..\..\..\..\test\sys\fd.c;..\..\..\..\test\sys\nanosleep.c;..\..\..\..\test\sys\timestamps.c;..\..\..\..\test\sys\perms.c;..\..\..\..\test\sys\self_path.c;..\..\..\..\test\sys\pipe\data.c;..\..\..\..\test\sys\pipe\blocking.c;..\..\..\..\test\sys\transfer.c;..\..\..\..\test\sys\readlink.c;..\..\..\..\test\sys\socket\data.c;..\..\..\..\test\sys\socket\listen.c;..\..\..\..\test\sys\socket\desc.c;..\..\..\..\test\sys\tty\contract.c;..\..\..\..\test\sys\tty\mode.c;..\..\..\..\test\sys\vtable.c;..\..\..\..\test\sys\wait\event.c;..\..\..\..\test\sys\wait\wait.c;..\..\..\..\test\sys\inherit.c + ..\..\..\..\test\main.c;..\..\..\..\test\sys\sys.c;..\..\..\..\test\sys\open.c;..\..\..\..\test\sys\path.c;..\..\..\..\test\sys\futex.c;..\..\..\..\test\sys\fd.c;..\..\..\..\test\sys\nanosleep.c;..\..\..\..\test\sys\timestamps.c;..\..\..\..\test\sys\perms.c;..\..\..\..\test\sys\self_path.c;..\..\..\..\test\sys\pipe\data.c;..\..\..\..\test\sys\pipe\blocking.c;..\..\..\..\test\sys\transfer.c;..\..\..\..\test\sys\readlink.c;..\..\..\..\test\sys\socket\data.c;..\..\..\..\test\sys\socket\listen.c;..\..\..\..\test\sys\socket\desc.c;..\..\..\..\test\sys\tty\contract.c;..\..\..\..\test\sys\tty\mode.c;..\..\..\..\test\sys\wait\event.c;..\..\..\..\test\sys\wait\wait.c;..\..\..\..\test\sys\inherit.c ..\..\..\..\sp.h;..\..\..\..\sp\sp_test.h false From 59c3b4ab4fb17de03d91934a0e152d0979c7c9db Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Tue, 22 Sep 2026 17:53:40 -0400 Subject: [PATCH 11/72] fs: stop relying on parent creation in atomic tests --- test/fs/atomic.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/test/fs/atomic.c b/test/fs/atomic.c index edbf7443..91723893 100644 --- a/test/fs/atomic.c +++ b/test/fs/atomic.c @@ -52,12 +52,12 @@ static const test_t open_at_tests [] = { }, { .name = "exclusive_new", - .path = "A/B", + .path = "B", .content = "B", .op = OP_EXCLUSIVE, .expect = { .paths = { - { .path = "A/B", .exists = true, .kind = SP_FS_KIND_FILE, .content = "B" }, + { .path = "B", .exists = true, .kind = SP_FS_KIND_FILE, .content = "B" }, }, }, }, @@ -134,6 +134,7 @@ static const test_t staged_tests [] = { { .name = "exclusive_existing", .setup = { + { "S", FS_SETUP_DIR }, { .path = "A", .content = "old" }, }, .path = "A", @@ -149,6 +150,7 @@ static const test_t staged_tests [] = { { .name = "parent_is_file", .setup = { + { "S", FS_SETUP_DIR }, { .path = "P", .content = "P" }, }, .path = "P/B", @@ -164,6 +166,7 @@ static const test_t staged_tests [] = { { .name = "exclusive_parent_is_file", .setup = { + { "S", FS_SETUP_DIR }, { .path = "P", .content = "P" }, }, .path = "P/B", @@ -193,6 +196,9 @@ static const test_t staged_tests [] = { }, { .name = "abort_leaves_nothing", + .setup = { + { "S", FS_SETUP_DIR }, + }, .path = "A/B", .content = "B", .op = OP_ABORT, From 38e4428f843772c17d49f3bd76d282e58dda8758 Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Tue, 22 Sep 2026 17:54:08 -0400 Subject: [PATCH 12/72] fs: copy tests for no parent creation and open-first source --- test/fs/copy.c | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/test/fs/copy.c b/test/fs/copy.c index 1a495a5e..5dbbcfff 100644 --- a/test/fs/copy.c +++ b/test/fs/copy.c @@ -55,15 +55,15 @@ static const test_t tests [] = { }, }, { - .name = "file_creates_parents", + .name = "file_missing_parent", .setup = { { .path = "A", .content = "A" }, }, .src = "A", - .dst = "D/E/B", + .dst = "D/B", + .err = SP_ERR_SYS_NOT_FOUND, .expect = { - { .path = "D/E", .exists = true, .kind = SP_FS_KIND_DIR }, - { .path = "D/E/B", .exists = true, .kind = SP_FS_KIND_FILE, .content = "A" }, + { .path = "D" }, }, }, { @@ -194,20 +194,6 @@ static const test_t tests [] = { { .path = "B" }, }, }, -#if defined(SP_POSIX) - { - .name = "file_source_is_fifo", - .setup = { - { .path = "F", .kind = FS_SETUP_FIFO }, - }, - .src = "F", - .dst = "B", - .err = SP_ERR_SYS_UNSUPPORTED, - .expect = { - { .path = "B" }, - }, - }, -#endif { .name = "file_dest_parent_is_file", .setup = { From 12a203a794d67585a7d6eb574b49a3b81acca6a2 Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Tue, 22 Sep 2026 17:54:08 -0400 Subject: [PATCH 13/72] fs: atomic tests for no parent creation --- test/fs/atomic.c | 41 +++++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/test/fs/atomic.c b/test/fs/atomic.c index 91723893..f69d88cf 100644 --- a/test/fs/atomic.c +++ b/test/fs/atomic.c @@ -24,15 +24,14 @@ typedef struct { // the process root. static const test_t open_at_tests [] = { { - .name = "nested_parents_created", - .path = "A/B/C", - .content = "C", + .name = "missing_parent", + .path = "A/B", + .content = "B", .op = OP_REPLACE, .expect = { + .err = SP_ERR_SYS_NOT_FOUND, .paths = { - { .path = "A", .exists = true, .kind = SP_FS_KIND_DIR }, - { .path = "A/B", .exists = true, .kind = SP_FS_KIND_DIR }, - { .path = "A/B/C", .exists = true, .kind = SP_FS_KIND_FILE, .content = "C" }, + { .path = "A" }, }, }, }, @@ -93,41 +92,47 @@ static const test_t open_at_tests [] = { }, { .name = "abort_leaves_nothing", - .path = "A/B", - .content = "B", + .path = "A", + .content = "A", .op = OP_ABORT, .expect = { .paths = { - { .path = "A", .exists = true, .kind = SP_FS_KIND_DIR }, - { .path = "A/B" }, + { .path = "A" }, }, }, }, }; // The temp lives in a staging directory "S" under the sandbox, so the -// destination's parent is first probed by the commit, not the open. +// destination's parent is first seen by the commit, not the open. static const test_t staged_tests [] = { { - .name = "parent_created_at_commit", - .path = "A/B/C", - .content = "C", + .name = "missing_parent_at_commit", + .setup = { + { "S", FS_SETUP_DIR }, + }, + .path = "A/B", + .content = "B", .op = OP_REPLACE, .expect = { + .err = SP_ERR_SYS_NOT_FOUND, .paths = { - { .path = "S", .exists = true, .kind = SP_FS_KIND_DIR }, - { .path = "A/B/C", .exists = true, .kind = SP_FS_KIND_FILE, .content = "C" }, + { .path = "A" }, }, }, }, { - .name = "exclusive_parent_created_at_commit", + .name = "exclusive_missing_parent_at_commit", + .setup = { + { "S", FS_SETUP_DIR }, + }, .path = "A/B", .content = "B", .op = OP_EXCLUSIVE, .expect = { + .err = SP_ERR_SYS_NOT_FOUND, .paths = { - { .path = "A/B", .exists = true, .kind = SP_FS_KIND_FILE, .content = "B" }, + { .path = "A" }, }, }, }, From d3bd89aa00014c4932e541aadc8f361256c4a79b Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Tue, 22 Sep 2026 17:54:08 -0400 Subject: [PATCH 14/72] fs: write_atomic test for no parent creation --- test/fs/write_atomic.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/fs/write_atomic.c b/test/fs/write_atomic.c index f1e8d45e..1feef3df 100644 --- a/test/fs/write_atomic.c +++ b/test/fs/write_atomic.c @@ -34,6 +34,17 @@ static const test_t tests [] = { }, }, }, + { + .name = "missing_parent", + .path = "D/A", + .content = "A", + .expect = { + .err = SP_ERR_SYS_NOT_FOUND, + .paths = { + { .path = "D" }, + }, + }, + }, { .name = "replace_existing", .setup = { From 4739169fadfdb85e281777dd42e557923471aebf Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Tue, 22 Sep 2026 17:57:51 -0400 Subject: [PATCH 15/72] fs: NT reports a file parent as not found in atomic tests --- test/fs/atomic.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/fs/atomic.c b/test/fs/atomic.c index f69d88cf..a4a42edb 100644 --- a/test/fs/atomic.c +++ b/test/fs/atomic.c @@ -84,7 +84,12 @@ static const test_t open_at_tests [] = { .content = "B", .op = OP_REPLACE, .expect = { + // a file in the parent chain: POSIX reports ENOTDIR, NT reports PATH_NOT_FOUND +#if defined(SP_WIN32) + .err = SP_ERR_SYS_NOT_FOUND, +#else .err = SP_ERR_SYS_NOT_DIR, +#endif .paths = { { .path = "P", .exists = true, .kind = SP_FS_KIND_FILE, .content = "P" }, }, @@ -193,7 +198,12 @@ static const test_t staged_tests [] = { .content = "A", .op = OP_REPLACE, .expect = { + // a file in the parent chain: POSIX reports ENOTDIR, NT reports PATH_NOT_FOUND +#if defined(SP_WIN32) + .err = SP_ERR_SYS_NOT_FOUND, +#else .err = SP_ERR_SYS_NOT_DIR, +#endif .paths = { { .path = "S", .exists = true, .kind = SP_FS_KIND_FILE, .content = "S" }, }, From c0b8b4adaf017b307388cf6c44346078f60b0f80 Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Tue, 22 Sep 2026 17:57:51 -0400 Subject: [PATCH 16/72] fs: platform-correct parent-is-file and exclusive symlink copy cases --- test/fs/copy.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/test/fs/copy.c b/test/fs/copy.c index 5dbbcfff..acc5168e 100644 --- a/test/fs/copy.c +++ b/test/fs/copy.c @@ -202,7 +202,12 @@ static const test_t tests [] = { }, .src = "A", .dst = "P/B", + // a file in the parent chain: POSIX reports ENOTDIR, NT reports PATH_NOT_FOUND +#if defined(SP_WIN32) + .err = SP_ERR_SYS_NOT_FOUND, +#else .err = SP_ERR_SYS_NOT_DIR, +#endif .expect = { { .path = "A", .exists = true, .kind = SP_FS_KIND_FILE, .content = "A" }, { .path = "P", .exists = true, .kind = SP_FS_KIND_FILE, .content = "P" }, @@ -376,8 +381,8 @@ static const test_t tests [] = { { "A", FS_SETUP_DIR }, { .path = "A/C", .content = "C" }, { .path = "A/L", .kind = FS_SETUP_SYMLINK, .target = "A/C" }, - { "E", FS_SETUP_DIR }, - { .path = "E/L", .kind = FS_SETUP_SYMLINK, .target = "A" }, + { .path = "E/F", .content = "F" }, + { .path = "E/L", .kind = FS_SETUP_SYMLINK, .target = "E/F" }, }, .op = OP_COPY_TREE, .mode = SP_FS_ATOMIC_EXCLUSIVE, @@ -385,7 +390,7 @@ static const test_t tests [] = { .dst = "E", .err = SP_ERR_SYS_EXISTS, .expect = { - { .path = "E/L", .exists = true, .kind = SP_FS_KIND_SYMLINK, .target = "A" }, + { .path = "E/L", .exists = true, .kind = SP_FS_KIND_SYMLINK, .target = "E/F" }, }, }, { From f23c61d6dcab47a030edb9116811a841f97fe6d6 Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Tue, 22 Sep 2026 23:10:52 -0400 Subject: [PATCH 17/72] more --- sp.h | 86 +++++++++++++++++++++--------------------------- test/fs/atomic.c | 12 ------- 2 files changed, 37 insertions(+), 61 deletions(-) diff --git a/sp.h b/sp.h index 1ab7bd0a..0cbd2dc5 100644 --- a/sp.h +++ b/sp.h @@ -20356,10 +20356,6 @@ SP_PRIVATE sp_err_t sp_fs_atomic_create(sp_fs_atomic_t* af, sp_sys_fd_t dir, sp_ u32 flags = SP_SYS_OPEN_CREATE | SP_SYS_OPEN_EXCLUSIVE; sp_sys_fd_t fd = SP_SYS_INVALID_FD; sp_err_t err = sp_sys_open_s(dir, af->temp, SP_SYS_OPEN_MODE_WO, flags, &fd); - if (err == SP_ERR_SYS_NOT_FOUND) { - err = sp_fs_create_parent_at(dir, af->temp); - if (!err) err = sp_sys_open_s(dir, af->temp, SP_SYS_OPEN_MODE_WO, flags, &fd); - } if (err) { *af = sp_zero_s(sp_fs_atomic_t); return err; @@ -20382,7 +20378,7 @@ sp_err_t sp_fs_atomic_open(sp_fs_atomic_t* af, sp_str_t path) { } sp_err_t sp_fs_atomic_open_staged_at(sp_fs_atomic_t* af, sp_path_t path, sp_str_t staging) { - if (sp_str_empty(staging)) return SP_ERR_SYS_BUG; + sp_assert(!sp_str_empty(staging)); return sp_fs_atomic_create(af, path.dir, path.sub, staging); } @@ -20414,14 +20410,7 @@ sp_err_t sp_fs_atomic_commit(sp_fs_atomic_t* af, sp_fs_atomic_mode_t mode) { sp_err_t close_err = sp_io_file_writer_close(&af->writer); if (!err) err = close_err; - if (!err) { - err = sp_fs_atomic_publish(af, mode); - if (err == SP_ERR_SYS_NOT_FOUND) { - err = sp_fs_create_parent_at(af->dir, af->path); - if (!err) err = sp_fs_atomic_publish(af, mode); - } - } - + if (!err) err = sp_fs_atomic_publish(af, mode); if (err) sp_sys_unlink_s(af->dir, af->temp); af->temp = sp_zero_s(sp_str_t); return err; @@ -20482,27 +20471,6 @@ sp_err_t sp_fs_write_atomic_cstr(sp_str_t path, const c8* str) { return sp_fs_write_atomic_cstr_at(sp_path_at_root(path), str); } -SP_PRIVATE sp_err_t sp_fs_copy_bytes_at(sp_path_t from, sp_path_t to, sp_fs_atomic_mode_t mode, sp_sys_file_perms_t perms) { - sp_io_file_reader_t reader = sp_zero; - sp_try(sp_io_file_reader_from_path_at(&reader, from)); - - sp_fs_atomic_t af = sp_zero; - sp_err_t err = sp_fs_atomic_open_at(&af, to); - if (err) { - sp_io_file_reader_close(&reader); - return err; - } - - err = sp_io_copy(sp_fs_atomic_writer(&af), &reader.base, SP_NULLPTR); - sp_io_file_reader_close(&reader); - if (!err) err = sp_sys_set_file_perms_s(af.dir, af.temp, perms); - if (err) { - sp_fs_atomic_abort(&af); - return err; - } - return sp_fs_atomic_commit(&af, mode); -} - SP_PRIVATE sp_err_t sp_fs_copy_link_at(sp_path_t from, sp_path_t to, sp_fs_atomic_mode_t mode) { c8 buf [SP_PATH_MAX]; sp_str_t target = sp_zero; @@ -20510,12 +20478,9 @@ SP_PRIVATE sp_err_t sp_fs_copy_link_at(sp_path_t from, sp_path_t to, sp_fs_atomi switch (mode) { case SP_FS_ATOMIC_REPLACE: { - c8 temp_buf [SP_PATH_MAX]; - sp_str_t temp = sp_fs_temp_name(sp_fs_parent_path(to.sub), sp_fs_get_name(to.sub), temp_buf); - sp_try(sp_sys_symlink_s(target, to.dir, temp)); - sp_err_t err = sp_sys_rename_s(to.dir, temp, to.dir, to.sub); - if (err) sp_sys_unlink_s(to.dir, temp); - return err; + sp_err_t err = sp_sys_unlink_s(to.dir, to.sub); + if (err && err != SP_ERR_SYS_NOT_FOUND) return err; + return sp_sys_symlink_s(target, to.dir, to.sub); } case SP_FS_ATOMIC_EXCLUSIVE: { return sp_sys_symlink_s(target, to.dir, to.sub); @@ -20525,16 +20490,39 @@ SP_PRIVATE sp_err_t sp_fs_copy_link_at(sp_path_t from, sp_path_t to, sp_fs_atomi } sp_err_t sp_fs_copy_file_at(sp_path_t from, sp_path_t to, sp_fs_atomic_mode_t mode) { + sp_io_file_reader_t reader = sp_zero; + sp_try(sp_io_file_reader_from_path_at(&reader, from)); + sp_sys_file_meta_t meta = sp_zero; - sp_try(sp_sys_get_path_metadata_s(from.dir, from.sub, &meta)); + sp_err_t err = sp_sys_get_file_metadata(reader.file, &meta); + if (!err) { + switch (meta.kind) { + case SP_FS_KIND_FILE: break; + case SP_FS_KIND_DIR: err = SP_ERR_SYS_IS_DIR; break; + case SP_FS_KIND_SYMLINK: + case SP_FS_KIND_NONE: err = SP_ERR_SYS_UNSUPPORTED; break; + } + } + if (err) { + sp_io_file_reader_close(&reader); + return err; + } - switch (meta.kind) { - case SP_FS_KIND_FILE: return sp_fs_copy_bytes_at(from, to, mode, meta.perms); - case SP_FS_KIND_DIR: return SP_ERR_SYS_IS_DIR; - case SP_FS_KIND_SYMLINK: - case SP_FS_KIND_NONE: return SP_ERR_SYS_UNSUPPORTED; + sp_fs_atomic_t af = sp_zero; + err = sp_fs_atomic_open_at(&af, to); + if (err) { + sp_io_file_reader_close(&reader); + return err; } - SP_UNREACHABLE_RETURN(SP_OK); + + err = sp_io_copy(sp_fs_atomic_writer(&af), &reader.base, SP_NULLPTR); + sp_io_file_reader_close(&reader); + if (!err) err = sp_sys_set_file_perms_s(af.dir, af.temp, meta.perms); + if (err) { + sp_fs_atomic_abort(&af); + return err; + } + return sp_fs_atomic_commit(&af, mode); } typedef struct { @@ -20601,7 +20589,7 @@ sp_err_t sp_fs_copy_tree_at(sp_path_t from, sp_path_t to, sp_fs_atomic_mode_t mo switch (meta.kind) { case SP_FS_KIND_FILE: { - err = sp_fs_copy_bytes_at(src, dst, mode, meta.perms); + err = sp_fs_copy_file_at(src, dst, mode); break; } case SP_FS_KIND_SYMLINK: { @@ -20634,7 +20622,7 @@ sp_err_t sp_fs_copy_at(sp_path_t from, sp_path_t to, sp_fs_atomic_mode_t mode) { sp_try(sp_sys_get_path_metadata_s(from.dir, from.sub, &meta)); switch (meta.kind) { - case SP_FS_KIND_FILE: return sp_fs_copy_bytes_at(from, to, mode, meta.perms); + case SP_FS_KIND_FILE: return sp_fs_copy_file_at(from, to, mode); case SP_FS_KIND_DIR: return sp_fs_copy_tree_at(from, to, mode); case SP_FS_KIND_SYMLINK: case SP_FS_KIND_NONE: return SP_ERR_SYS_UNSUPPORTED; diff --git a/test/fs/atomic.c b/test/fs/atomic.c index a4a42edb..97c525dd 100644 --- a/test/fs/atomic.c +++ b/test/fs/atomic.c @@ -273,15 +273,3 @@ sp_test_each(fs, atomic_staged, test_t, staged_tests) { fs_expect_no_temps(t, sandbox); return SP_OK; } - -sp_test(fs, atomic_staged_empty_staging_is_bug) { - sp_sys_fd_t dir = SP_SYS_INVALID_FD; - sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sp_test_dir(t), &dir)); - - sp_path_t path = { .dir = dir, .sub = sp_str_lit("A") }; - - sp_fs_atomic_t af = sp_zero; - sp_expect_err_eq(t, sp_fs_atomic_open_staged_at(&af, path, sp_str_lit("")), SP_ERR_SYS_BUG); - sp_sys_close(dir); - return SP_OK; -} From 41be7714ead02517f5daf6ff3bf266622ae1f247 Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Wed, 23 Sep 2026 02:01:18 -0400 Subject: [PATCH 18/72] remove_dir --- sp.h | 118 +++++++++++++++--- sp/sp_msvc.h | 4 +- spn.toml | 4 +- test/fs/atomic.c | 4 +- test/fs/canonicalize.c | 2 +- test/fs/copy.c | 6 +- test/fs/create_dir.c | 2 +- test/fs/create_file.c | 2 +- test/fs/dir/os.c | 6 +- test/fs/dir/sim.c | 2 +- test/fs/fd_relative.c | 2 +- test/fs/it/os.c | 2 +- test/fs/links.c | 2 +- test/fs/mod_time.c | 4 +- test/fs/predicates.c | 2 +- test/fs/remove.c | 166 ------------------------- test/fs/remove/os.c | 272 +++++++++++++++++++++++++++++++++++++++++ test/fs/remove/sim.c | 110 +++++++++++++++++ test/fs/sim.c | 63 +++++++++- test/fs/sim.h | 12 ++ test/fs/write_atomic.c | 2 +- test/io/io.h | 2 +- test/sys/fd.c | 2 +- test/sys/path.c | 2 +- test/sys/sys.c | 4 +- test/sys/sys.h | 4 +- test/sys/transfer.c | 2 +- 27 files changed, 583 insertions(+), 220 deletions(-) delete mode 100644 test/fs/remove.c create mode 100644 test/fs/remove/os.c create mode 100644 test/fs/remove/sim.c diff --git a/sp.h b/sp.h index 0cbd2dc5..44e9af1d 100644 --- a/sp.h +++ b/sp.h @@ -1413,6 +1413,10 @@ typedef enum { SP_SYS_OPEN_APPEND = 1 << 3, } sp_sys_open_flags_t; +typedef enum { + SP_SYS_OPEN_DIR_NO_FOLLOW = 1 << 0, +} sp_sys_open_dir_flags_t; + typedef enum { SP_SYS_BLOCKING, SP_SYS_NONBLOCKING, @@ -1456,7 +1460,7 @@ SP_API s64 sp_sys_get_storage_path(c8* buf, u64 size); SP_API s64 sp_sys_get_config_path(c8* buf, u64 size); SP_API s64 sp_sys_get_fd_path(sp_sys_fd_t fd, c8* buf, u64 size); SP_API sp_err_t sp_sys_open(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_open_mode_t mode, u32 flags, sp_sys_fd_t* out); -SP_API sp_err_t sp_sys_open_dir(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_fd_t* out); +SP_API sp_err_t sp_sys_open_dir(sp_sys_fd_t fd, const c8* path, u32 len, u32 flags, sp_sys_fd_t* out); SP_API sp_err_t sp_sys_close(sp_sys_fd_t fd); SP_API sp_err_t sp_sys_pipe(sp_sys_pipe_t* pipe, sp_sys_pipe_desc_t desc); SP_API sp_err_t sp_sys_pipe_ready(sp_sys_fd_t fd, u8* ready); @@ -1516,7 +1520,7 @@ SP_API sp_err_t sp_sys_chdir(const c8* path, u32 len); SP_API sp_err_t sp_sys_open_s(sp_sys_fd_t fd, sp_str_t path, sp_sys_open_mode_t mode, u32 flags, sp_sys_fd_t* out); -SP_API sp_err_t sp_sys_open_dir_s(sp_sys_fd_t fd, sp_str_t path, sp_sys_fd_t* out); +SP_API sp_err_t sp_sys_open_dir_s(sp_sys_fd_t fd, sp_str_t path, u32 flags, sp_sys_fd_t* out); SP_API sp_err_t sp_sys_get_path_metadata_s(sp_sys_fd_t fd, sp_str_t path, sp_sys_file_meta_t* st); SP_API sp_err_t sp_sys_get_link_metadata_s(sp_sys_fd_t fd, sp_str_t path, sp_sys_file_meta_t* st); SP_API sp_err_t sp_sys_mkdir_s(sp_sys_fd_t fd, sp_str_t path, sp_sys_file_perms_t perms); @@ -1555,7 +1559,7 @@ typedef struct { s64 (*get_config_path)(c8* buf, u64 size); s64 (*get_fd_path)(sp_sys_fd_t fd, c8* buf, u64 size); sp_err_t (*open)(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_open_mode_t mode, u32 flags, sp_sys_fd_t* out); - sp_err_t (*open_dir)(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_fd_t*); + sp_err_t (*open_dir)(sp_sys_fd_t fd, const c8* path, u32 len, u32 flags, sp_sys_fd_t*); sp_err_t (*close)(sp_sys_fd_t fd); sp_err_t (*pipe)(sp_sys_pipe_t* pipe, sp_sys_pipe_desc_t desc); sp_err_t (*pipe_ready)(sp_sys_fd_t fd, u8* ready); @@ -1630,7 +1634,7 @@ SP_API s64 sp_sys_get_storage_path_p(c8* buf, u64 size); SP_API s64 sp_sys_get_config_path_p(c8* buf, u64 size); SP_API s64 sp_sys_get_fd_path_p(sp_sys_fd_t fd, c8* buf, u64 size); SP_API sp_err_t sp_sys_open_p(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_open_mode_t mode, u32 flags, sp_sys_fd_t* out); -SP_API sp_err_t sp_sys_open_dir_p(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_fd_t* out); +SP_API sp_err_t sp_sys_open_dir_p(sp_sys_fd_t fd, const c8* path, u32 len, u32 flags, sp_sys_fd_t* out); SP_API sp_err_t sp_sys_close_p(sp_sys_fd_t fd); SP_API sp_err_t sp_sys_pipe_p(sp_sys_pipe_t* pipe, sp_sys_pipe_desc_t desc); SP_API sp_err_t sp_sys_pipe_ready_p(sp_sys_fd_t fd, u8* ready); @@ -3120,6 +3124,7 @@ SP_API sp_err_t sp_fs_create_file_slice_at(sp_path_t path, sp_mem_sl SP_API sp_err_t sp_fs_create_file_cstr(sp_str_t path, const c8* str); SP_API sp_err_t sp_fs_create_file_cstr_at(sp_path_t path, const c8* str); SP_API sp_err_t sp_fs_remove_dir(sp_str_t path); +SP_API sp_err_t sp_fs_remove_dir_at(sp_path_t path); SP_API sp_err_t sp_fs_remove_file(sp_str_t path); SP_API sp_err_t sp_fs_remove_file_at(sp_path_t path); SP_API sp_err_t sp_fs_create_hard_link(sp_str_t target, sp_str_t link_path); @@ -3129,7 +3134,7 @@ SP_API sp_err_t sp_fs_create_sym_link_at(sp_str_t target, sp_path_t SP_API sp_err_t sp_fs_copy_at(sp_path_t from, sp_path_t to, sp_fs_atomic_mode_t mode); SP_API sp_err_t sp_fs_copy_file_at(sp_path_t from, sp_path_t to, sp_fs_atomic_mode_t mode); SP_API sp_err_t sp_fs_copy_tree_at(sp_path_t from, sp_path_t to, sp_fs_atomic_mode_t mode); -SP_API sp_err_t sp_fs_dir_open(sp_fs_dir_t* it, sp_sys_fd_t fd, sp_str_t path, sp_mem_slice_t buf); +SP_API sp_err_t sp_fs_dir_open(sp_fs_dir_t* it, sp_sys_fd_t fd, sp_str_t path, u32 flags, sp_mem_slice_t buf); SP_API sp_err_t sp_fs_dir_next(sp_fs_dir_t* it, sp_fs_dir_entry_t* out); SP_API sp_err_t sp_fs_dir_close(sp_fs_dir_t* it); SP_API sp_fs_it_t sp_fs_it_new(sp_mem_t mem, sp_str_t path); @@ -5350,8 +5355,10 @@ SP_IMP DWORD WINAPI sp_win32_thread_launch(LPVOID args); #define SP_SYS_LINUX_O_PATH 010000000 #if defined(SP_AMD64) #define SP_SYS_LINUX_O_DIRECTORY 0200000 + #define SP_SYS_LINUX_O_NOFOLLOW 0400000 #elif defined(SP_ARM64) #define SP_SYS_LINUX_O_DIRECTORY 040000 + #define SP_SYS_LINUX_O_NOFOLLOW 0100000 #endif #define SP_SYS_LINUX_STATX_BASIC_STATS 0x7ff @@ -5883,8 +5890,8 @@ sp_err_t sp_sys_open(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_open_mode_t return (sp_rt.vt->open)(fd, path, len, mode, flags, out); } -sp_err_t sp_sys_open_dir(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_fd_t* out) { - return (sp_rt.vt->open_dir)(fd, path, len, out); +sp_err_t sp_sys_open_dir(sp_sys_fd_t fd, const c8* path, u32 len, u32 flags, sp_sys_fd_t* out) { + return (sp_rt.vt->open_dir)(fd, path, len, flags, out); } sp_err_t sp_sys_close(sp_sys_fd_t fd) { @@ -8353,25 +8360,42 @@ sp_err_t sp_sys_open_p(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_open_mode #endif } -sp_err_t sp_sys_open_dir_p(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_fd_t* out) { +sp_err_t sp_sys_open_dir_p(sp_sys_fd_t fd, const c8* path, u32 len, u32 flags, sp_sys_fd_t* out) { *out = SP_SYS_INVALID_FD; + if (flags & ~(u32)SP_SYS_OPEN_DIR_NO_FOLLOW) return SP_ERR_SYS_INVALID; #if defined(SP_WIN32) u32 access = SYNCHRONIZE | FILE_READ_ATTRIBUTES | FILE_READ_DATA | FILE_READ_EA; u32 share = FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE; u32 options = SP_NT_FILE_SYNCHRONOUS_IO_NONALERT | SP_NT_FILE_OPEN_FOR_BACKUP_INTENT | SP_NT_FILE_DIRECTORY_FILE; + if (flags & SP_SYS_OPEN_DIR_NO_FOLLOW) options |= SP_NT_FILE_OPEN_REPARSE_POINT; sp_sys_fd_t handle = SP_SYS_INVALID_FD; sp_nt_status_t status = sp_sys_nt_open(fd, sp_str(path, len), access, share, SP_NT_FILE_OPEN, options, FILE_ATTRIBUTE_NORMAL, &handle); if (!SP_NT_SUCCESS(status)) return sp_sys_err_from_nt(status); + if (flags & SP_SYS_OPEN_DIR_NO_FOLLOW) { + BY_HANDLE_FILE_INFORMATION info = sp_zero; + if (!GetFileInformationByHandle((HANDLE)handle, &info)) { + sp_err_t err = sp_sys_err_from_win32(GetLastError()); + sp_sys_nt_close(handle); + return err; + } + if (info.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { + sp_sys_nt_close(handle); + return SP_ERR_SYS_LOOP; + } + } + *out = handle; return SP_OK; #elif defined(SP_LINUX) c8 buf [SP_PATH_MAX]; sp_try(sp_sys_posix_path(path, len, buf)); - s64 rc = sp_syscall(SP_SYSCALL_NUM_OPENAT, fd, buf, SP_SYS_LINUX_O_RDONLY | SP_SYS_LINUX_O_DIRECTORY | SP_SYS_LINUX_O_CLOEXEC, 0); + u32 o = SP_SYS_LINUX_O_RDONLY | SP_SYS_LINUX_O_DIRECTORY | SP_SYS_LINUX_O_CLOEXEC; + if (flags & SP_SYS_OPEN_DIR_NO_FOLLOW) o |= SP_SYS_LINUX_O_NOFOLLOW; + s64 rc = sp_syscall(SP_SYSCALL_NUM_OPENAT, fd, buf, o, 0); if (rc < 0) return sp_sys_err_from_errno(-rc); *out = (sp_sys_fd_t)rc; @@ -8380,14 +8404,16 @@ sp_err_t sp_sys_open_dir_p(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_fd_t* #elif defined(SP_MACOS) || defined(SP_COSMO) c8 buf [SP_PATH_MAX]; sp_try(sp_sys_posix_path(path, len, buf)); - s32 rc = openat((int)fd, buf, O_RDONLY | O_DIRECTORY | O_CLOEXEC, 0); + s32 o = O_RDONLY | O_DIRECTORY | O_CLOEXEC; + if (flags & SP_SYS_OPEN_DIR_NO_FOLLOW) o |= O_NOFOLLOW; + s32 rc = openat((int)fd, buf, o, 0); if (rc < 0) return sp_sys_err_from_errno(errno); *out = (sp_sys_fd_t)rc; return SP_OK; #elif defined(SP_WASM) - (void)fd; (void)path; (void)len; + (void)fd; (void)path; (void)len; (void)flags; return SP_ERR_SYS_UNSUPPORTED; #else @@ -8400,8 +8426,8 @@ sp_err_t sp_sys_open_s(sp_sys_fd_t fd, sp_str_t path, sp_sys_open_mode_t mode, u return sp_sys_open(fd, path.data, path.len, mode, flags, out); } -sp_err_t sp_sys_open_dir_s(sp_sys_fd_t fd, sp_str_t path, sp_sys_fd_t* out) { - return sp_sys_open_dir(fd, path.data, path.len, out); +sp_err_t sp_sys_open_dir_s(sp_sys_fd_t fd, sp_str_t path, u32 flags, sp_sys_fd_t* out) { + return sp_sys_open_dir(fd, path.data, path.len, flags, out); } ///////////////////// @@ -20124,12 +20150,12 @@ sp_err_t sp_fs_remove_file(sp_str_t path) { return sp_fs_remove_file_at(sp_path_at_root(path)); } -sp_err_t sp_fs_dir_open(sp_fs_dir_t* it, sp_sys_fd_t fd, sp_str_t path, sp_mem_slice_t buf) { +sp_err_t sp_fs_dir_open(sp_fs_dir_t* it, sp_sys_fd_t fd, sp_str_t path, u32 flags, sp_mem_slice_t buf) { *it = sp_zero_s(sp_fs_dir_t); sp_assert(buf.len >= SP_SYS_DIR_MIN_BUF); sp_sys_fd_t dir_fd = SP_SYS_INVALID_FD; - sp_try(sp_sys_open_dir_s(fd, path, &dir_fd)); + sp_try(sp_sys_open_dir_s(fd, path, flags, &dir_fd)); sp_err_t err = sp_sys_dir_it_open(dir_fd, &it->dir); if (err) { @@ -20187,7 +20213,7 @@ SP_PRIVATE sp_err_t sp_fs_it_join(sp_fs_it_t* it, u32 prefix, sp_str_t name, u32 SP_PRIVATE sp_err_t sp_fs_it_push(sp_fs_it_t* it, u32 len) { sp_fs_it_frame_t frame = { .len = len }; - sp_try(sp_fs_dir_open(&frame.dir, it->dir, sp_str(it->path, len), sp_mem_slice(frame.buf, SP_FS_IT_BUF_SIZE))); + sp_try(sp_fs_dir_open(&frame.dir, it->dir, sp_str(it->path, len), 0, sp_mem_slice(frame.buf, SP_FS_IT_BUF_SIZE))); sp_da_push(it->stack, frame); return SP_OK; } @@ -20323,6 +20349,62 @@ sp_err_t sp_fs_remove_dir(sp_str_t path) { return err; } +/* + sp_fs_remove_dir_at(path) + + Iterate the entries produced by a path and fd, depth-first and traversing + into subdirectories, and unlink them. Fails if this root (i.e. fd + path) + does not refer to a directory, and unlinks the root at the end. + + This function uses O(depth) memory. It can be used with directories of + arbitrary depth, and each directory is opened exactly once. + + + # SYMLINKS + +  When the path is a symlink, this function unlinks rather than resolving the + link and removing the contents in the link target. POSIX semantics say that + a trailing slash on the path follows the link implicitly. This is silly, we + don't encode arguments implicitly in strings. + + + # ERRORS + + ## NOT_FOUND + + When you ask to remove D, but D doesn't exist, we return NOT_FOUND. Often, + callers just want to *ensure* a directory's absence, and they don't care if + their call removed it, or someone else, or if it never existed. By returning + NOT_FOUND, we also let callers who DO care handle the case when they + expected something to exist but it didn't. + + When you ask to remove D, we may encounter NOT_FOUND while removing something + in D's tree. We do not report such errors; these aren't paths that the caller + named. For example, iterating D might yield D/M/W. The caller never specified + D/M/W. They asked us to remove everything under D, which is satisfied. + + The combination of these two give a nice property: NOT_FOUND is returned if + and only if nothing was removed. + + + ## FAIL FAST + + If removing D/M/W gives us some other error (e.g. it's read only), we + immediately fail. It's coherent to keep going, trying every entry that you + can, and sometimes what you want when cleaning up. But it's full of subtle + hidden rules, and it's much simpler for caller and library if we just tell + you that something failed and let you decide. + + + ## FINAL REMOVE CAN FAIL + + Once + + */ +SP_API sp_err_t sp_fs_remove_dir_at(sp_path_t path) { + return SP_OK; +} + static sp_atomic_s32_t sp_fs_atomic_sequence; SP_PRIVATE sp_str_t sp_fs_temp_name(sp_str_t dir, sp_str_t name, c8 buf [SP_PATH_MAX]) { @@ -20532,10 +20614,10 @@ typedef struct { } sp_fs_copy_frame_t; SP_PRIVATE sp_err_t sp_fs_copy_frame_open(sp_fs_copy_frame_t* frame, sp_path_t from, sp_path_t to) { - sp_try(sp_fs_dir_open(&frame->from, from.dir, from.sub, sp_mem_slice(frame->buf, SP_FS_IT_BUF_SIZE))); + sp_try(sp_fs_dir_open(&frame->from, from.dir, from.sub, 0, sp_mem_slice(frame->buf, SP_FS_IT_BUF_SIZE))); sp_err_t err = sp_fs_create_dir_at(to); - if (!err) err = sp_sys_open_dir_s(to.dir, to.sub, &frame->to); + if (!err) err = sp_sys_open_dir_s(to.dir, to.sub, 0, &frame->to); if (err) sp_fs_dir_close(&frame->from); return err; } diff --git a/sp/sp_msvc.h b/sp/sp_msvc.h index 1b347c2c..d2d5d1c6 100644 --- a/sp/sp_msvc.h +++ b/sp/sp_msvc.h @@ -453,7 +453,7 @@ static sp_msvc_err_t sp_msvc_find_sdks(sp_msvc_t* msvc, sp_msvc_arch_t arch) { SP_ALIGNED u8 dir_buf [SP_SYS_DIR_MIN_BUF]; sp_fs_dir_t dir = sp_zero; - if (sp_fs_dir_open(&dir, sp_sys_get_root(0), lib, sp_mem_slice(dir_buf, sizeof(dir_buf)))) { + if (sp_fs_dir_open(&dir, sp_sys_get_root(0), lib, 0, sp_mem_slice(dir_buf, sizeof(dir_buf)))) { return SP_MSVC_ERR_SDK_NOT_FOUND; } @@ -500,7 +500,7 @@ static sp_msvc_err_t sp_msvc_find_installations(sp_msvc_t* msvc, sp_msvc_arch_t SP_ALIGNED u8 dir_buf [SP_SYS_DIR_MIN_BUF]; sp_fs_dir_t dir = sp_zero; - if (sp_fs_dir_open(&dir, sp_sys_get_root(0), instances.value, sp_mem_slice(dir_buf, sizeof(dir_buf)))) { + if (sp_fs_dir_open(&dir, sp_sys_get_root(0), instances.value, 0, sp_mem_slice(dir_buf, sizeof(dir_buf)))) { return SP_MSVC_ERR_VS_NOT_FOUND; } diff --git a/spn.toml b/spn.toml index 706432f0..6fad142d 100644 --- a/spn.toml +++ b/spn.toml @@ -22,7 +22,7 @@ source = [ "test/sys/tty/contract.c", "test/sys/tty/mode.c", "test/sys/wait/event.c", "test/sys/wait/wait.c", "test/sys/inherit.c", - "test/fs/dir/*.c", "test/fs/it/*.c", "test/fs/wtf8/*.c", + "test/fs/dir/*.c", "test/fs/it/*.c", "test/fs/remove/*.c", "test/fs/wtf8/*.c", "test/fs/*.c", "test/fs/windows/nt_path.c", "test/msvc/*.c", "test/mem/uninitialized.c", @@ -54,7 +54,7 @@ source = ["test/main.c", "test/atomic.c"] [[test]] name = "fs" -source = ["test/main.c", "test/fs/dir/*.c", "test/fs/it/*.c", "test/fs/wtf8/*.c", "test/fs/*.c", "test/fs/windows/nt_path.c"] +source = ["test/main.c", "test/fs/dir/*.c", "test/fs/it/*.c", "test/fs/remove/*.c", "test/fs/wtf8/*.c", "test/fs/*.c", "test/fs/windows/nt_path.c"] include = [".", "test/fs"] [[test]] diff --git a/test/fs/atomic.c b/test/fs/atomic.c index 97c525dd..d39065e1 100644 --- a/test/fs/atomic.c +++ b/test/fs/atomic.c @@ -243,7 +243,7 @@ sp_test_each(fs, atomic_open_at, test_t, open_at_tests) { fs_apply_setup(t, sandbox, it->setup); sp_sys_fd_t dir = SP_SYS_INVALID_FD; - sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, &dir)); + sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, 0, &dir)); sp_fs_atomic_t af = sp_zero; sp_err_t err = drive(&af, sp_fs_atomic_open_at(&af, (sp_path_t) { .dir = dir, .sub = sp_str_view(it->path) }), it); @@ -260,7 +260,7 @@ sp_test_each(fs, atomic_staged, test_t, staged_tests) { fs_apply_setup(t, sandbox, it->setup); sp_sys_fd_t dir = SP_SYS_INVALID_FD; - sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, &dir)); + sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, 0, &dir)); sp_path_t path = { .dir = dir, .sub = sp_str_view(it->path) }; diff --git a/test/fs/canonicalize.c b/test/fs/canonicalize.c index cb204941..2b34f4f4 100644 --- a/test/fs/canonicalize.c +++ b/test/fs/canonicalize.c @@ -165,7 +165,7 @@ sp_test_each(fs, canonicalize, test_t, tests) { fs_apply_setup(t, sandbox, it->setup); sp_sys_fd_t dir = SP_SYS_INVALID_FD; - sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, &dir)); + sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, 0, &dir)); sp_path_t input = { .dir = dir, .sub = sp_str_view(it->input) }; sp_str_t result = sp_fs_canonicalize_path_at(mem, input); diff --git a/test/fs/copy.c b/test/fs/copy.c index acc5168e..68f6ed06 100644 --- a/test/fs/copy.c +++ b/test/fs/copy.c @@ -551,10 +551,10 @@ sp_test_each(fs, copy, test_t, tests) { fs_apply_setup(t, sandbox, it->setup); sp_sys_fd_t dir = SP_SYS_INVALID_FD; - sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, &dir)); + sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, 0, &dir)); sp_sys_fd_t dst_dir = SP_SYS_INVALID_FD; - sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), fs_path_c(t, it->dst_dir ? it->dst_dir : ""), &dst_dir)); + sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), fs_path_c(t, it->dst_dir ? it->dst_dir : ""), 0, &dst_dir)); sp_path_t src = { .dir = dir, .sub = sp_cstr_as_str(it->src) }; sp_path_t dst = { .dir = dst_dir, .sub = sp_cstr_as_str(it->dst) }; @@ -589,7 +589,7 @@ static const size_test_t sizes [] = { sp_test_each(fs, copy_size, size_test_t, sizes) { sp_mem_t mem = sp_test_arena(t); sp_sys_fd_t dir = SP_SYS_INVALID_FD; - sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sp_test_dir(t), &dir)); + sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sp_test_dir(t), 0, &dir)); sp_path_t from = { .dir = dir, .sub = sp_str_lit("A") }; sp_path_t to = { .dir = dir, .sub = sp_str_lit("B") }; diff --git a/test/fs/create_dir.c b/test/fs/create_dir.c index 7a855137..fe2467e9 100644 --- a/test/fs/create_dir.c +++ b/test/fs/create_dir.c @@ -137,7 +137,7 @@ sp_test_each(fs, create_dir, test_t, tests) { fs_apply_setup(t, sandbox, it->setup); sp_sys_fd_t dir = SP_SYS_INVALID_FD; - sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, &dir)); + sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, 0, &dir)); sp_path_t target = { dir, sp_cstr_as_str(it->target) }; sp_expect_err_eq(t, sp_fs_create_dir_at(target), it->expect.err); diff --git a/test/fs/create_file.c b/test/fs/create_file.c index 0c89752c..930aa108 100644 --- a/test/fs/create_file.c +++ b/test/fs/create_file.c @@ -75,7 +75,7 @@ sp_test_each(fs, create_file, test_t, tests) { fs_apply_setup(t, sandbox, it->setup); sp_sys_fd_t dir = SP_SYS_INVALID_FD; - sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, &dir)); + sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, 0, &dir)); sp_path_t path = { .dir = dir, .sub = sp_str_view(it->path) }; sp_err_t result = SP_OK; diff --git a/test/fs/dir/os.c b/test/fs/dir/os.c index 6647b2cd..e14cb71b 100644 --- a/test/fs/dir/os.c +++ b/test/fs/dir/os.c @@ -110,14 +110,14 @@ sp_test_each(fs, dir, test_t, tests) { sp_sys_fd_t sandbox_fd = SP_SYS_INVALID_FD; if (it->relative) { - sp_try(sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, &sandbox_fd)); + sp_try(sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, 0, &sandbox_fd)); } SP_ALIGNED u8 buf [SP_SYS_DIR_MIN_BUF]; sp_fs_dir_t iter = sp_zero; sp_err_t open_err = it->relative - ? sp_fs_dir_open(&iter, sandbox_fd, sp_cstr_as_str(it->dir), sp_mem_slice(buf, sizeof(buf))) - : sp_fs_dir_open(&iter, sp_sys_get_root(0), dir, sp_mem_slice(buf, sizeof(buf))); + ? sp_fs_dir_open(&iter, sandbox_fd, sp_cstr_as_str(it->dir), 0, sp_mem_slice(buf, sizeof(buf))) + : sp_fs_dir_open(&iter, sp_sys_get_root(0), dir, 0, sp_mem_slice(buf, sizeof(buf))); sp_expect_ok(t, open_err); if (!open_err) { diff --git a/test/fs/dir/sim.c b/test/fs/dir/sim.c index c2f608b7..cda2c93c 100644 --- a/test/fs/dir/sim.c +++ b/test/fs/dir/sim.c @@ -42,7 +42,7 @@ sp_test_each(fs, dir_sim, test_t, tests, .serial = true) { SP_ALIGNED u8 buf [SP_SYS_DIR_MIN_BUF]; sp_fs_dir_t dir = sp_zero; - sp_err_t open_err = sp_fs_dir_open(&dir, sp_sys_get_root(0), sp_str_lit("T"), sp_mem_slice(buf, sizeof(buf))); + sp_err_t open_err = sp_fs_dir_open(&dir, sp_sys_get_root(0), sp_str_lit("T"), 0, sp_mem_slice(buf, sizeof(buf))); sp_expect_err_eq(t, open_err, it->expect.open); if (!open_err) { diff --git a/test/fs/fd_relative.c b/test/fs/fd_relative.c index 388e1ad3..7450f58c 100644 --- a/test/fs/fd_relative.c +++ b/test/fs/fd_relative.c @@ -116,7 +116,7 @@ sp_test_each(fs, fd_relative, test_t, tests) { sp_str_t cwd_path = sp_fs_join_path(mem, sandbox, sp_str_view(it->cwd)); sp_sys_fd_t cwd = SP_SYS_INVALID_FD; - if (sp_sys_open_dir_s(sp_sys_get_root(0), cwd_path, &cwd) != SP_OK) { + if (sp_sys_open_dir_s(sp_sys_get_root(0), cwd_path, 0, &cwd) != SP_OK) { sp_test_fail(t, "failed to open dir {}", sp_fmt_str(cwd_path)); return SP_ERR; } diff --git a/test/fs/it/os.c b/test/fs/it/os.c index 961e3a7f..b8195f19 100644 --- a/test/fs/it/os.c +++ b/test/fs/it/os.c @@ -168,7 +168,7 @@ sp_test_each(fs, it, test_t, tests) { fs_apply_setup(t, sandbox, it->setup); sp_sys_fd_t dir = SP_SYS_INVALID_FD; - sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, &dir)); + sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, 0, &dir)); sp_str_t base = sp_str_lit("R"); sp_path_t root = { .dir = dir, .sub = sp_cstr_as_str(it->root ? it->root : "R") }; diff --git a/test/fs/links.c b/test/fs/links.c index d8ab2354..50adf378 100644 --- a/test/fs/links.c +++ b/test/fs/links.c @@ -125,7 +125,7 @@ sp_test_each(fs, links, test_t, tests) { fs_apply_setup(t, sandbox, it->setup); sp_sys_fd_t dir = SP_SYS_INVALID_FD; - sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, &dir)); + sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, 0, &dir)); sp_path_t target = { .dir = dir, .sub = sp_str_view(it->target) }; sp_path_t link = { .dir = dir, .sub = sp_str_view(it->link) }; diff --git a/test/fs/mod_time.c b/test/fs/mod_time.c index 5f4ede8e..ff96bcf8 100644 --- a/test/fs/mod_time.c +++ b/test/fs/mod_time.c @@ -2,7 +2,7 @@ sp_test(fs, mod_time_nonzero) { sp_sys_fd_t sandbox = SP_SYS_INVALID_FD; - sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sp_test_dir(t), &sandbox)); + sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sp_test_dir(t), 0, &sandbox)); sp_path_t file = { .dir = sandbox, .sub = sp_str_lit("A") }; sp_path_t dir = { .dir = sandbox, .sub = sp_str_lit("B") }; @@ -17,7 +17,7 @@ sp_test(fs, mod_time_nonzero) { sp_test(fs, mod_time_updates_after_write) { sp_sys_fd_t sandbox = SP_SYS_INVALID_FD; - sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sp_test_dir(t), &sandbox)); + sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sp_test_dir(t), 0, &sandbox)); sp_path_t file = { sandbox, sp_str_lit("A") }; sp_fs_create_file_str_at(file, sp_str_lit("A")); diff --git a/test/fs/predicates.c b/test/fs/predicates.c index a5ec4160..c4f12710 100644 --- a/test/fs/predicates.c +++ b/test/fs/predicates.c @@ -95,7 +95,7 @@ sp_test_each(fs, predicates, test_t, tests) { fs_apply_setup(t, sandbox, it->setup); sp_sys_fd_t dir = SP_SYS_INVALID_FD; - sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, &dir)); + sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, 0, &dir)); sp_carr_for(it->probes, i) { const probe_t* probe = &it->probes[i]; diff --git a/test/fs/remove.c b/test/fs/remove.c deleted file mode 100644 index d25d6c1f..00000000 --- a/test/fs/remove.c +++ /dev/null @@ -1,166 +0,0 @@ -#include "fs.h" - -typedef struct { - const c8* name; - fs_setup_t setup [FS_MAX_SETUP]; - bool dir; - const c8* path; - sp_err_t err; - fs_expected_path_t expect [FS_MAX_PATHS]; -} test_t; - -static const test_t tests [] = { - { - .name = "file_basic", - .setup = { - { "A" }, - }, - .path = "A", - .expect = { - { .path = "A" }, - }, - }, - { - .name = "file_missing", - .path = "A", - .err = SP_ERR_SYS_NOT_FOUND, - }, - { - .name = "file_is_dir", - .setup = { - { "A", FS_SETUP_DIR }, - }, - .path = "A", - .err = SP_ERR_SYS_IS_DIR, - .expect = { - { .path = "A", .exists = true, .kind = SP_FS_KIND_DIR }, - }, - }, - { - .name = "dir_recursive", - .setup = { - { "A", FS_SETUP_DIR }, - { "A/B" }, - { "A/C", FS_SETUP_DIR }, - { "A/C/D" }, - }, - .dir = true, - .path = "A", - .expect = { - { .path = "A" }, - { .path = "A/B" }, - { .path = "A/C" }, - { .path = "A/C/D" }, - }, - }, - { - .name = "dir_missing", - .dir = true, - .path = "A", - .err = SP_ERR_SYS_NOT_FOUND, - }, - { - .name = "dir_is_file", - .setup = { - { "A" }, - }, - .dir = true, - .path = "A", - .err = SP_ERR_SYS_NOT_DIR, - .expect = { - { .path = "A", .exists = true, .kind = SP_FS_KIND_FILE }, - }, - }, - { - .name = "dir_does_not_follow_symlink", - .setup = { - { "B" }, - { "A", FS_SETUP_DIR }, - { .path = "A/L", .kind = FS_SETUP_SYMLINK, .target = "B" }, - { "A/C" }, - }, - .dir = true, - .path = "A", - .expect = { - { .path = "A" }, - { .path = "A/L" }, - { .path = "A/C" }, - { .path = "B", .exists = true, .kind = SP_FS_KIND_FILE }, - }, - }, - { - .name = "dir_symlink_root_removes_link_only", - .setup = { - { "A", FS_SETUP_DIR }, - { "A/B" }, - { .path = "L", .kind = FS_SETUP_SYMLINK, .target = "A" }, - }, - .dir = true, - .path = "L", - .expect = { - { .path = "L" }, - { .path = "A", .exists = true, .kind = SP_FS_KIND_DIR }, - { .path = "A/B", .exists = true, .kind = SP_FS_KIND_FILE }, - }, - }, -#if defined(SP_POSIX) - { - .name = "dir_with_fifo", - .setup = { - { "A", FS_SETUP_DIR }, - { .path = "A/F", .kind = FS_SETUP_FIFO }, - { "A/B" }, - }, - .dir = true, - .path = "A", - .expect = { - { .path = "A" }, - { .path = "A/F" }, - { .path = "A/B" }, - }, - }, -#endif - { - .name = "unicode_file", - .setup = { - { "\xc3\xb6\x70\x65\x6e.txt" }, - }, - .path = "\xc3\xb6\x70\x65\x6e.txt", - .expect = { - { .path = "\xc3\xb6\x70\x65\x6e.txt" }, - }, - }, - { - .name = "unicode_dir", - .setup = { - { "\xc3\xa4\x62\x63", FS_SETUP_DIR }, - { "\xc3\xa4\x62\x63/\xc3\xbc\x66\x69\x6c\x65.txt" }, - }, - .dir = true, - .path = "\xc3\xa4\x62\x63", - .expect = { - { .path = "\xc3\xa4\x62\x63" }, - { .path = "\xc3\xa4\x62\x63/\xc3\xbc\x66\x69\x6c\x65.txt" }, - }, - }, -}; - -sp_test_each(fs, remove, test_t, tests) { - skip_if_symlinks_needed(t, it->setup); - - sp_str_t sandbox = sp_test_dir(t); - fs_apply_setup(t, sandbox, it->setup); - - sp_sys_fd_t dir = SP_SYS_INVALID_FD; - sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, &dir)); - - sp_path_t path = { .dir = dir, .sub = sp_str_view(it->path) }; - sp_err_t result = it->dir - ? sp_fs_remove_dir(sp_fs_join_path(sp_test_arena(t), sandbox, path.sub)) - : sp_fs_remove_file_at(path); - sp_sys_close(dir); - sp_expect_err_eq(t, result, it->err); - - fs_expect_paths(t, sandbox, it->expect); - return SP_OK; -} diff --git a/test/fs/remove/os.c b/test/fs/remove/os.c new file mode 100644 index 00000000..57c918c9 --- /dev/null +++ b/test/fs/remove/os.c @@ -0,0 +1,272 @@ +#include "fs.h" + +#define DEEP_LEVELS 40 +#define DEEP_NAME "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" \ + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + +typedef struct { + const c8* name; + fs_setup_t setup [FS_MAX_SETUP]; + bool dir; + const c8* cwd; + const c8* path; + sp_err_t err; + fs_expected_path_t expect [FS_MAX_PATHS]; +} test_t; + +static const test_t tests [] = { + { + .name = "file_basic", + .setup = { + { "A" }, + }, + .path = "A", + .expect = { + { .path = "A" }, + }, + }, + { + .name = "file_missing", + .path = "A", + .err = SP_ERR_SYS_NOT_FOUND, + }, + { + .name = "file_is_dir", + .setup = { + { "A", FS_SETUP_DIR }, + }, + .path = "A", + .err = SP_ERR_SYS_IS_DIR, + .expect = { + { .path = "A", .exists = true, .kind = SP_FS_KIND_DIR }, + }, + }, + { + .name = "dir_recursive", + .setup = { + { "A", FS_SETUP_DIR }, + { "A/B" }, + { "A/C", FS_SETUP_DIR }, + { "A/C/D" }, + { "A/E", FS_SETUP_DIR }, + }, + .dir = true, + .path = "A", + .expect = { + { .path = "A" }, + { .path = "A/B" }, + { .path = "A/C" }, + { .path = "A/C/D" }, + { .path = "A/E" }, + }, + }, + { + .name = "dir_nested_sub", + .setup = { + { "A/B/C" }, + }, + .dir = true, + .path = "A/B", + .expect = { + { .path = "A", .exists = true, .kind = SP_FS_KIND_DIR }, + { .path = "A/B" }, + }, + }, + { + .name = "dir_missing", + .dir = true, + .path = "A", + .err = SP_ERR_SYS_NOT_FOUND, + }, + { + .name = "dir_is_file", + .setup = { + { "A" }, + }, + .dir = true, + .path = "A", + .err = SP_ERR_SYS_NOT_DIR, + .expect = { + { .path = "A", .exists = true, .kind = SP_FS_KIND_FILE }, + }, + }, + { + .name = "dir_dot_rejected", + .setup = { + { "A/B" }, + }, + .dir = true, + .cwd = "A", + .path = ".", + .err = SP_ERR_SYS_INVALID, + .expect = { + { .path = "A/B", .exists = true, .kind = SP_FS_KIND_FILE }, + }, + }, + { + .name = "dir_dotdot_rejected", + .setup = { + { "A/B" }, + }, + .dir = true, + .path = "A/..", + .err = SP_ERR_SYS_INVALID, + .expect = { + { .path = "A/B", .exists = true, .kind = SP_FS_KIND_FILE }, + }, + }, + { + .name = "dir_empty_rejected", + .setup = { + { "A/B" }, + }, + .dir = true, + .cwd = "A", + .path = "", + .err = SP_ERR_SYS_INVALID, + .expect = { + { .path = "A/B", .exists = true, .kind = SP_FS_KIND_FILE }, + }, + }, + { + .name = "dir_does_not_follow_symlink", + .setup = { + { "B" }, + { "A", FS_SETUP_DIR }, + { .path = "A/L", .kind = FS_SETUP_SYMLINK, .target = "B" }, + { "A/C" }, + }, + .dir = true, + .path = "A", + .expect = { + { .path = "A" }, + { .path = "A/L" }, + { .path = "A/C" }, + { .path = "B", .exists = true, .kind = SP_FS_KIND_FILE }, + }, + }, + { + .name = "dir_symlink_root_removes_link_only", + .setup = { + { "A", FS_SETUP_DIR }, + { "A/B" }, + { .path = "L", .kind = FS_SETUP_SYMLINK, .target = "A" }, + }, + .dir = true, + .path = "L", + .expect = { + { .path = "L" }, + { .path = "A", .exists = true, .kind = SP_FS_KIND_DIR }, + { .path = "A/B", .exists = true, .kind = SP_FS_KIND_FILE }, + }, + }, + { + .name = "dir_symlink_root_trailing_sep_removes_link_only", + .setup = { + { "A", FS_SETUP_DIR }, + { "A/B" }, + { .path = "L", .kind = FS_SETUP_SYMLINK, .target = "A" }, + }, + .dir = true, + .path = "L/", + .expect = { + { .path = "L" }, + { .path = "A", .exists = true, .kind = SP_FS_KIND_DIR }, + { .path = "A/B", .exists = true, .kind = SP_FS_KIND_FILE }, + }, + }, +#if defined(SP_POSIX) + { + .name = "dir_with_fifo", + .setup = { + { "A", FS_SETUP_DIR }, + { .path = "A/F", .kind = FS_SETUP_FIFO }, + { "A/B" }, + }, + .dir = true, + .path = "A", + .expect = { + { .path = "A" }, + { .path = "A/F" }, + { .path = "A/B" }, + }, + }, +#endif +}; + +sp_test_each(fs, remove, test_t, tests) { + skip_if_symlinks_needed(t, it->setup); + + sp_mem_t mem = sp_test_arena(t); + sp_str_t sandbox = sp_test_dir(t); + fs_apply_setup(t, sandbox, it->setup); + + sp_str_t base = it->cwd ? sp_fs_join_path(mem, sandbox, sp_cstr_as_str(it->cwd)) : sandbox; + sp_sys_fd_t dir = SP_SYS_INVALID_FD; + sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), base, 0, &dir)); + + sp_path_t path = { .dir = dir, .sub = sp_cstr_as_str(it->path) }; + sp_err_t result = it->dir ? sp_fs_remove_dir_at(path) : sp_fs_remove_file_at(path); + sp_expect_err_eq(t, result, it->err); + + sp_sys_file_meta_t meta = sp_zero; + sp_expect_ok(t, sp_sys_get_file_metadata(dir, &meta)); + sp_sys_close(dir); + + fs_expect_paths(t, sandbox, it->expect); + return SP_OK; +} + +sp_test(fs, remove_dir_deeper_than_path_max) { + sp_sys_fd_t dir = SP_SYS_INVALID_FD; + sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sp_test_dir(t), 0, &dir)); + + sp_path_t root = { .dir = dir, .sub = sp_str_lit("A") }; + sp_must_ok(t, sp_fs_create_dir_at(root)); + + sp_sys_fd_t cur = SP_SYS_INVALID_FD; + sp_must_ok(t, sp_sys_open_dir_s(root.dir, root.sub, 0, &cur)); + sp_for(level, DEEP_LEVELS) { + sp_path_t child = { .dir = cur, .sub = sp_str_lit(DEEP_NAME) }; + sp_must_ok(t, sp_fs_create_dir_at(child)); + + sp_sys_fd_t next = SP_SYS_INVALID_FD; + sp_must_ok(t, sp_sys_open_dir_s(child.dir, child.sub, 0, &next)); + sp_sys_close(cur); + cur = next; + } + sp_must_ok(t, sp_fs_create_file_at((sp_path_t) { .dir = cur, .sub = sp_str_lit("F") })); + sp_sys_close(cur); + + sp_expect_ok(t, sp_fs_remove_dir_at(root)); + sp_expect(t, !sp_fs_exists_at(root)); + sp_sys_close(dir); + return SP_OK; +} + +#if defined(SP_POSIX) +sp_test(fs, remove_dir_unwritable_subdir_fails) { + sp_mem_t mem = sp_test_arena(t); + sp_str_t locked = fs_path_c(t, "A/B"); + sp_must_ok(t, sp_fs_create_dir(locked)); + sp_must_ok(t, sp_fs_create_file(fs_path_c(t, "A/B/C"))); + + const c8* locked_c = sp_cstr_from_str(mem, locked); + sp_must_eq(t, chmod(locked_c, 0555), 0); + + sp_sys_fd_t dir = SP_SYS_INVALID_FD; + sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sp_test_dir(t), 0, &dir)); + sp_err_t result = sp_fs_remove_dir_at((sp_path_t) { .dir = dir, .sub = sp_str_lit("A") }); + sp_sys_close(dir); + sp_must_eq(t, chmod(locked_c, 0755), 0); + + if (!result) return sp_test_skip(t, "directory permissions not enforced"); + sp_expect_err_eq(t, result, SP_ERR_SYS_ACCESS_DENIED); + fs_expect_paths(t, sp_test_dir(t), (const fs_expected_path_t [FS_MAX_PATHS]) { + { .path = "A", .exists = true, .kind = SP_FS_KIND_DIR }, + { .path = "A/B", .exists = true, .kind = SP_FS_KIND_DIR }, + { .path = "A/B/C", .exists = true, .kind = SP_FS_KIND_FILE }, + }); + return SP_OK; +} +#endif diff --git a/test/fs/remove/sim.c b/test/fs/remove/sim.c new file mode 100644 index 00000000..b87c311e --- /dev/null +++ b/test/fs/remove/sim.c @@ -0,0 +1,110 @@ +#include "fs.h" +#include "sim.h" + +typedef struct { + const c8* path; + sp_fs_kind_t kind; +} removed_t; + +typedef struct { + sp_err_t err; + removed_t removed [SIM_MAX_REMOVED]; + u32 opens; +} expect_t; + +typedef struct { + const c8* name; + sim_dir_t dirs [SIM_MAX_DIRS]; + expect_t expect; +} test_t; + +static const test_t tests [] = { + { + .name = "entry_vanished_is_ok", + .dirs = { + { .path = "T", .entries = { { "B", SP_FS_KIND_FILE, .unlink = SP_ERR_SYS_NOT_FOUND }, { "C", SP_FS_KIND_FILE } } }, + }, + .expect = { + .removed = { { "T/C", SP_FS_KIND_FILE }, { "T", SP_FS_KIND_DIR } }, + .opens = 1, + }, + }, + { + .name = "root_vanished_after_walk_is_ok", + .dirs = { + { .path = "T", .rmdir = SP_ERR_SYS_NOT_FOUND, .entries = { { "B", SP_FS_KIND_FILE } } }, + }, + .expect = { + .removed = { { "T/B", SP_FS_KIND_FILE } }, + .opens = 1, + }, + }, + { + .name = "file_hint_that_is_dir_is_walked", + .dirs = { + { .path = "T", .entries = { { "B", SP_FS_KIND_FILE, .unlink = SP_ERR_SYS_IS_DIR } } }, + { .path = "T/B", .entries = { { "C", SP_FS_KIND_FILE } } }, + }, + .expect = { + .removed = { { "T/B/C", SP_FS_KIND_FILE }, { "T/B", SP_FS_KIND_DIR }, { "T", SP_FS_KIND_DIR } }, + .opens = 2, + }, + }, + { + .name = "dir_hint_that_is_not_dir_is_unlinked", + .dirs = { + { .path = "T", .entries = { { "B", SP_FS_KIND_DIR } } }, + { .path = "T/B", .open = SP_ERR_SYS_NOT_DIR }, + }, + .expect = { + .removed = { { "T/B", SP_FS_KIND_FILE }, { "T", SP_FS_KIND_DIR } }, + .opens = 2, + }, + }, + { + .name = "not_empty_propagates_without_rescan", + .dirs = { + { .path = "T", .rmdir = SP_ERR_SYS_NOT_EMPTY }, + }, + .expect = { + .err = SP_ERR_SYS_NOT_EMPTY, + .opens = 1, + }, + }, + { + .name = "read_error_unwinds", + .dirs = { + { .path = "T", .entries = { { "B", SP_FS_KIND_DIR }, { "C", SP_FS_KIND_FILE } } }, + { .path = "T/B", .read = SP_ERR_SYS_IO }, + }, + .expect = { + .err = SP_ERR_SYS_IO, + .opens = 2, + }, + }, +}; + +sp_test_each(fs, remove_sim, test_t, tests, .serial = true) { + sim_t s = sp_zero; + sim_begin(&s, it->dirs); + sp_err_t err = sp_fs_remove_dir_at((sp_path_t) { .dir = sp_sys_get_root(0), .sub = sp_str_lit("T") }); + sim_end(&s); + + sp_expect_err_eq(t, err, it->expect.err); + + fs_match_t matches [SIM_MAX_REMOVED] = sp_zero; + u32 n = 0; + sp_carr_for(it->expect.removed, i) { + const removed_t* want = &it->expect.removed[i]; + if (!want->path) break; + matches[n++] = (fs_match_t) { .key = sp_cstr_as_str(want->path), .kind = want->kind }; + } + sp_for(i, s.num_removed) { + fs_match(t, matches, n, sp_cstr_as_str(s.removed[i].path), s.removed[i].kind); + } + fs_match_finish(t, matches, n); + + sp_expect_eq(t, s.count.opens, it->expect.opens); + sp_expect_eq(t, s.count.closes, s.count.dirs); + return SP_OK; +} diff --git a/test/fs/sim.c b/test/fs/sim.c index e9e43cad..f7347ea5 100644 --- a/test/fs/sim.c +++ b/test/fs/sim.c @@ -35,10 +35,32 @@ static u32 dir_len(const sim_dir_t* dir) { return n; } -static sp_err_t open_dir(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_fd_t* out) { +static sp_str_t resolve(sp_sys_fd_t fd, const c8* path, u32 len, c8 buf [SP_PATH_MAX]) { + s64 slot = (s64)fd - FD_BASE; + if (slot < 0 || slot >= (s64)active->count.dirs) return sp_str(path, len); + + sp_str_t base = sp_cstr_as_str(active->opened[slot].dir->path); + sp_assert(base.len + 1 + len < SP_PATH_MAX); + sp_str_copy_to(base, buf, SP_PATH_MAX); + buf[base.len] = '/'; + sp_mem_copy(buf + base.len + 1, path, len); + return sp_str(buf, base.len + 1 + len); +} + +static void record(sp_str_t path, sp_fs_kind_t kind) { + sp_assert(active->num_removed < SIM_MAX_REMOVED); + sp_assert(path.len < SIM_MAX_REMOVED_PATH); + sim_removed_t* out = &active->removed[active->num_removed++]; + sp_str_copy_to(path, out->path, SIM_MAX_REMOVED_PATH); + out->kind = kind; +} + +static sp_err_t open_dir(sp_sys_fd_t fd, const c8* path, u32 len, u32 flags, sp_sys_fd_t* out) { active->count.opens++; - const sim_dir_t* dir = find_dir(sp_str(path, len)); + c8 buf [SP_PATH_MAX]; + const sim_dir_t* dir = find_dir(resolve(fd, path, len, buf)); if (!dir) return SP_ERR_SYS_NOT_FOUND; + if (dir->open) return dir->open; sp_assert(active->count.dirs < SIM_MAX_OPENS); active->opened[active->count.dirs] = (sim_open_t) { .dir = dir }; *out = (sp_sys_fd_t)(FD_BASE + active->count.dirs); @@ -85,10 +107,39 @@ static sp_err_t dir_it_close(sp_sys_dir_it_t* it) { } static sp_err_t get_link_metadata(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_file_meta_t* st) { - const sim_entry_t* entry = find_entry(sp_str(path, len)); - if (!entry) return SP_ERR_SYS_NOT_FOUND; + c8 buf [SP_PATH_MAX]; + sp_str_t full = resolve(fd, path, len, buf); *st = sp_zero_s(sp_sys_file_meta_t); - st->kind = entry->stat; + + const sim_entry_t* entry = find_entry(full); + if (entry) { + st->kind = entry->stat; + return SP_OK; + } + if (find_dir(full)) { + st->kind = SP_FS_KIND_DIR; + return SP_OK; + } + return SP_ERR_SYS_NOT_FOUND; +} + +static sp_err_t unlink_entry(sp_sys_fd_t fd, const c8* path, u32 len) { + c8 buf [SP_PATH_MAX]; + sp_str_t full = resolve(fd, path, len, buf); + const sim_entry_t* entry = find_entry(full); + if (!entry) return SP_ERR_SYS_NOT_FOUND; + if (entry->unlink) return entry->unlink; + record(full, SP_FS_KIND_FILE); + return SP_OK; +} + +static sp_err_t remove_dir(sp_sys_fd_t fd, const c8* path, u32 len) { + c8 buf [SP_PATH_MAX]; + sp_str_t full = resolve(fd, path, len, buf); + const sim_dir_t* dir = find_dir(full); + if (!dir) return SP_ERR_SYS_NOT_FOUND; + if (dir->rmdir) return dir->rmdir; + record(full, SP_FS_KIND_DIR); return SP_OK; } @@ -101,6 +152,8 @@ void sim_begin(sim_t* sim, const sim_dir_t* dirs) { sim->vt.dir_it_parse = dir_it_parse; sim->vt.dir_it_close = dir_it_close; sim->vt.get_link_metadata = get_link_metadata; + sim->vt.unlink = unlink_entry; + sim->vt.rmdir = remove_dir; sim->saved = sp_sys_set_vtable(&sim->vt); active = sim; } diff --git a/test/fs/sim.h b/test/fs/sim.h index 062042e5..01b45598 100644 --- a/test/fs/sim.h +++ b/test/fs/sim.h @@ -6,17 +6,22 @@ #define SIM_MAX_DIRS 4 #define SIM_MAX_ENTRIES 4 #define SIM_MAX_OPENS 4 +#define SIM_MAX_REMOVED 8 +#define SIM_MAX_REMOVED_PATH 64 typedef struct { const c8* name; sp_fs_kind_t kind; sp_fs_kind_t stat; + sp_err_t unlink; } sim_entry_t; typedef struct { const c8* path; + sp_err_t open; sp_err_t it_open; sp_err_t read; + sp_err_t rmdir; sim_entry_t entries [SIM_MAX_ENTRIES]; } sim_dir_t; @@ -32,10 +37,17 @@ typedef struct { u32 fd_closes; } sim_count_t; +typedef struct { + c8 path [SIM_MAX_REMOVED_PATH]; + sp_fs_kind_t kind; +} sim_removed_t; + typedef struct { const sim_dir_t* dirs; sim_count_t count; sim_open_t opened [SIM_MAX_OPENS]; + sim_removed_t removed [SIM_MAX_REMOVED]; + u32 num_removed; sp_sys_vtable_t vt; const sp_sys_vtable_t* saved; } sim_t; diff --git a/test/fs/write_atomic.c b/test/fs/write_atomic.c index 1feef3df..bc763a80 100644 --- a/test/fs/write_atomic.c +++ b/test/fs/write_atomic.c @@ -65,7 +65,7 @@ sp_test_each(fs, write_atomic, test_t, tests) { fs_apply_setup(t, sandbox, it->setup); sp_sys_fd_t dir = SP_SYS_INVALID_FD; - sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, &dir)); + sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, 0, &dir)); sp_path_t path = { .dir = dir, .sub = sp_str_view(it->path) }; sp_err_t err = sp_fs_write_atomic_at(path, sp_str_view(it->content)); diff --git a/test/io/io.h b/test/io/io.h index 31e91d30..b7b9a362 100644 --- a/test/io/io.h +++ b/test/io/io.h @@ -20,7 +20,7 @@ UTEST_F_SETUP(io) { sp_test_file_manager_init(&ut.file_manager); ut.file.sub = sp_str_lit("sp_io_rw.file"); sp_test_file_create_empty(&ut.file_manager, ut.file.sub); - sp_sys_open_dir_s(sp_sys_get_root(0), ut.file_manager.paths.test, &ut.file.dir); + sp_sys_open_dir_s(sp_sys_get_root(0), ut.file_manager.paths.test, 0, &ut.file.dir); } UTEST_F_TEARDOWN(io) { diff --git a/test/sys/fd.c b/test/sys/fd.c index eabe93aa..026e0f17 100644 --- a/test/sys/fd.c +++ b/test/sys/fd.c @@ -32,7 +32,7 @@ static sp_err_t run(sp_test_t* t, test_t* c) { case OP_WRITE: err = sp_sys_write(GARBAGE_FD, "A", 1, &n); break; case OP_OPEN_DIR: { sp_sys_fd_t out = SP_SYS_INVALID_FD; - err = sp_sys_open_dir_s(GARBAGE_FD, sp_str_lit("A"), &out); + err = sp_sys_open_dir_s(GARBAGE_FD, sp_str_lit("A"), 0, &out); if (!err) sp_sys_close(out); break; } diff --git a/test/sys/path.c b/test/sys/path.c index 62b30029..68fc3442 100644 --- a/test/sys/path.c +++ b/test/sys/path.c @@ -395,7 +395,7 @@ static sp_err_t run(sp_test_t* t, test_t* c) { sp_mem_t mem = sp_test_arena(t); sp_str_t sandbox = sp_test_dir(t); sp_sys_fd_t sandbox_fd = SP_SYS_INVALID_FD; - sp_try(sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, &sandbox_fd)); + sp_try(sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, 0, &sandbox_fd)); if (c->file) sp_fs_create_file_str(sp_fs_join_path(mem, sandbox, sp_cstr_as_str(c->file)), sp_str_lit("A")); if (c->dir) sp_fs_create_dir(sp_fs_join_path(mem, sandbox, sp_cstr_as_str(c->dir))); diff --git a/test/sys/sys.c b/test/sys/sys.c index c4e8524e..f441ebc1 100644 --- a/test/sys/sys.c +++ b/test/sys/sys.c @@ -56,7 +56,7 @@ sp_err_t sys_case_run(sp_test_t* t, sys_case_t* c) { return sp_test_skip(t, "symlinks not available"); } - sp_try(sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, &sandbox_fd)); + sp_try(sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, 0, &sandbox_fd)); sp_carr_for(c->setup, it) { sys_setup_t* ent = &c->setup[it]; @@ -104,7 +104,7 @@ sp_err_t sys_case_run(sp_test_t* t, sys_case_t* c) { case SYS_STEP_OPEN_DIR: { sp_sys_fd_t fd = SP_SYS_INVALID_FD; sp_str_t path = sp_cstr_as_str(step->open_dir.path); - sp_err_t err = sp_sys_open_dir_s(sandbox_fd, path, &fd); + sp_err_t err = sp_sys_open_dir_s(sandbox_fd, path, 0, &fd); sys_check_err(t, err, step->open_dir.err); if (!err) { if (step->open_dir.err) sp_sys_close(fd); diff --git a/test/sys/sys.h b/test/sys/sys.h index ea9afac9..1a05efae 100644 --- a/test/sys/sys.h +++ b/test/sys/sys.h @@ -244,7 +244,7 @@ static void run_sys_test(s32* utest_result, sys_test_t t) { } sp_str_t sandbox = sp_test_file_create_dir(&fm, t.label); - if (sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, &sandbox_fd)) { + if (sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, 0, &sandbox_fd)) { SP_TEST_REPORT("failed to open sandbox {}", sp_fmt_str(sandbox)); SP_FAIL(); goto done; @@ -275,7 +275,7 @@ static void run_sys_test(s32* utest_result, sys_test_t t) { } case SYS_STEP_OPEN_DIR: { sp_sys_fd_t fd = SP_SYS_INVALID_FD; - if (sp_sys_open_dir_s(sandbox_fd, sp_cstr_as_str(step->open_dir.path), &fd)) { + if (sp_sys_open_dir_s(sandbox_fd, sp_cstr_as_str(step->open_dir.path), 0, &fd)) { SP_TEST_REPORT("failed to open_dir {}", sp_fmt_cstr(step->open_dir.path)); SP_FAIL(); } diff --git a/test/sys/transfer.c b/test/sys/transfer.c index 73e34125..80bb0748 100644 --- a/test/sys/transfer.c +++ b/test/sys/transfer.c @@ -122,7 +122,7 @@ static sp_err_t run(sp_test_t* t, test_t* c) { sp_sys_fd_t dst_r = SP_SYS_INVALID_FD; u64 len = sp_cstr_len(c->data); - sp_try(sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, &sandbox_fd)); + sp_try(sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, 0, &sandbox_fd)); switch (c->src) { case END_FILE: { From 879219a270d36d186b53b51b6e2820931aed1364 Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Wed, 23 Sep 2026 17:34:01 -0400 Subject: [PATCH 19/72] remove_dir 2 --- sp.h | 137 +++++++++++++++++++++++------------- test/fs/get_name_for.c | 23 ++++++ test/fs/remove/os.c | 156 ++++++++++++++++++++++++++++------------- test/fs/remove/sim.c | 65 ++++++++++++----- test/fs/sim.c | 79 ++++++++++++--------- test/fs/sim.h | 9 ++- 6 files changed, 323 insertions(+), 146 deletions(-) create mode 100644 test/fs/get_name_for.c diff --git a/sp.h b/sp.h index 44e9af1d..e6c98401 100644 --- a/sp.h +++ b/sp.h @@ -3081,6 +3081,7 @@ typedef struct { for (sp_fs_it_t it = sp_fs_it_new_recursive(mem, dir); sp_fs_it_next(&it);) SP_API sp_str_t sp_fs_get_name(sp_str_t path); +SP_API sp_str_t sp_fs_get_name_for(sp_str_t path, sp_fs_path_kind_t kind); SP_API sp_str_t sp_fs_parent_path(sp_str_t path); SP_API sp_str_t sp_fs_trim_path(sp_str_t path); SP_API sp_str_t sp_fs_normalize_path(sp_mem_t mem, sp_str_t path); @@ -19758,6 +19759,14 @@ sp_str_t sp_fs_get_name(sp_str_t path) { return sp_str_suffix(path, ((s32)path.len) - it - 1); } +sp_str_t sp_fs_get_name_for(sp_str_t path, sp_fs_path_kind_t kind) { + if (kind != SP_FS_PATH_WINDOWS) return sp_fs_get_name(path); + + s32 slash = sp_str_find_c8_reverse(path, '/'); + s32 backslash = sp_str_find_c8_reverse(path, '\\'); + return sp_str_suffix(path, ((s32)path.len) - sp_max(slash, backslash) - 1); +} + sp_str_t sp_fs_trim_path(sp_str_t path) { if (sp_str_empty(path)) return path; @@ -20299,54 +20308,29 @@ sp_err_t sp_fs_collect_recursive(sp_mem_t mem, sp_str_t path, sp_da(sp_fs_entry_ return sp_fs_collect_it(mem, &it, out); } -sp_err_t sp_fs_remove_dir(sp_str_t path) { - sp_sys_file_meta_t st = sp_zero; - sp_try(sp_sys_get_link_metadata_s(sp_sys_get_root(0), path, &st)); - - switch (st.kind) { - case SP_FS_KIND_DIR: { - break; - } - case SP_FS_KIND_SYMLINK: { - // remove the link itself, never the target; windows directory - // symlinks can only be removed as directories, so flip on refusal - sp_err_t err = sp_fs_remove_file(path); - if (err == SP_ERR_SYS_IS_DIR) err = sp_sys_rmdir_s(sp_sys_get_root(0), path); - return err; - } - default: { - return SP_ERR_SYS_NOT_DIR; - } - } - - sp_err_t err = SP_OK; - sp_mem_arena_marker_t s = sp_mem_begin_scratch(); - - sp_da(sp_fs_entry_t) entries; - err = sp_fs_collect(s.mem, path, &entries); - if (err) goto done; - - sp_da_for(entries, i) { - sp_fs_entry_t* entry = &entries[i]; - - // entry kinds are hints; flip on a contradicting error so a lying or - // absent d_type can't strand an entry - if (entry->kind == SP_FS_KIND_DIR) { - err = sp_fs_remove_dir(entry->path); - if (err == SP_ERR_SYS_NOT_DIR) err = sp_fs_remove_file(entry->path); - } - else { - err = sp_fs_remove_file(entry->path); - if (err == SP_ERR_SYS_IS_DIR) err = sp_fs_remove_dir(entry->path); - } - if (err) goto done; - } +typedef struct { + sp_fs_dir_t dir; + SP_ALIGNED u8 buf [SP_FS_IT_BUF_SIZE]; + u32 name_offset; + u32 name_len; +} sp_fs_remove_frame_t; + +SP_PRIVATE sp_err_t sp_fs_remove_frame_push(sp_da(sp_fs_remove_frame_t)* stack, sp_path_t path, u32 name_offset) { + sp_fs_remove_frame_t frame = { .name_offset = name_offset, .name_len = path.sub.len }; + sp_try(sp_fs_dir_open(&frame.dir, path.dir, path.sub, SP_SYS_OPEN_DIR_NO_FOLLOW, sp_mem_slice(frame.buf, SP_FS_IT_BUF_SIZE))); + sp_da_push(*stack, frame); + return SP_OK; +} - err = sp_sys_rmdir_s(sp_sys_get_root(0), path); +SP_PRIVATE sp_path_t sp_fs_remove_frame_path(sp_fs_remove_frame_t* parent, sp_fs_remove_frame_t* frame) { + return (sp_path_t) { + .dir = parent->dir.dir.fd, + .sub = sp_str(sp_ptr_cast(const c8*, parent->buf) + frame->name_offset, frame->name_len), + }; +} -done: - sp_mem_end_scratch(s); - return err; +sp_err_t sp_fs_remove_dir(sp_str_t path) { + return sp_fs_remove_dir_at(sp_path_at_root(path)); } /* @@ -20401,8 +20385,65 @@ sp_err_t sp_fs_remove_dir(sp_str_t path) { Once */ -SP_API sp_err_t sp_fs_remove_dir_at(sp_path_t path) { - return SP_OK; +sp_err_t sp_fs_remove_dir_at(sp_path_t path) { + sp_str_t sub = sp_fs_trim_path(path.sub); + sp_str_t name = sp_fs_get_name_for(sub, sp_os_get_path_kind()); + if (sp_str_empty(name) || sp_str_equal(name, sp_str_lit(".")) || sp_str_equal(name, sp_str_lit(".."))) { + return SP_ERR_SYS_INVALID; + } + + sp_sys_file_meta_t meta = sp_zero; + sp_try(sp_sys_get_link_metadata_s(path.dir, sub, &meta)); + switch (meta.kind) { + case SP_FS_KIND_DIR: break; + case SP_FS_KIND_SYMLINK: return sp_sys_unlink_s(path.dir, sub); + case SP_FS_KIND_FILE: + case SP_FS_KIND_NONE: return SP_ERR_SYS_NOT_DIR; + } + + sp_path_t root = { .dir = path.dir, .sub = sub }; + sp_mem_arena_marker_t s = sp_mem_begin_scratch(); + sp_da(sp_fs_remove_frame_t) stack = sp_da_new(s.mem, sp_fs_remove_frame_t); + sp_err_t err = sp_fs_remove_frame_push(&stack, root, 0); + + while (!err && !sp_da_empty(stack)) { + u64 depth = sp_da_size(stack) - 1; + sp_fs_remove_frame_t* top = &stack[depth]; + top->dir.buf.data = top->buf; + + sp_fs_dir_entry_t entry = sp_zero; + err = sp_fs_dir_next(&top->dir, &entry); + if (err) break; + + if (!entry.name.data) { + sp_path_t self = depth ? sp_fs_remove_frame_path(&stack[depth - 1], top) : root; + sp_fs_dir_close(&top->dir); + err = sp_sys_rmdir_s(self.dir, self.sub); + sp_da_pop(stack); + } + else { + sp_path_t child = { .dir = top->dir.dir.fd, .sub = entry.name }; + u32 offset = (u32)(sp_ptr_cast(const u8*, entry.name.data) - top->buf); + + // entry kinds are hints; flip on a contradicting error so a lying or + // absent d_type can't strand an entry + if (entry.kind == SP_FS_KIND_DIR) { + err = sp_fs_remove_frame_push(&stack, child, offset); + if (err == SP_ERR_SYS_NOT_DIR || err == SP_ERR_SYS_LOOP) err = sp_sys_unlink_s(child.dir, child.sub); + } + else { + err = sp_sys_unlink_s(child.dir, child.sub); + if (err == SP_ERR_SYS_IS_DIR) err = sp_fs_remove_frame_push(&stack, child, offset); + } + } + if (err == SP_ERR_SYS_NOT_FOUND) err = SP_OK; + } + + sp_da_for(stack, it) { + sp_fs_dir_close(&stack[it].dir); + } + sp_mem_end_scratch(s); + return err; } static sp_atomic_s32_t sp_fs_atomic_sequence; diff --git a/test/fs/get_name_for.c b/test/fs/get_name_for.c new file mode 100644 index 00000000..5f5c1dc0 --- /dev/null +++ b/test/fs/get_name_for.c @@ -0,0 +1,23 @@ +#include "sp.h" +#include "sp/sp_test.h" + +typedef struct { + const c8* name; + sp_fs_path_kind_t kind; + const c8* input; + const c8* expect; +} test_t; + +static const test_t tests [] = { + { .name = "windows_backslash", .kind = SP_FS_PATH_WINDOWS, .input = "C:\\A\\B.txt", .expect = "B.txt" }, + { .name = "windows_mixed", .kind = SP_FS_PATH_WINDOWS, .input = "A\\B/C", .expect = "C" }, + { .name = "windows_dotdot", .kind = SP_FS_PATH_WINDOWS, .input = "A\\..", .expect = ".." }, + { .name = "windows_drive_root", .kind = SP_FS_PATH_WINDOWS, .input = "C:\\", .expect = "" }, + { .name = "posix_backslash", .kind = SP_FS_PATH_POSIX, .input = "A\\B", .expect = "A\\B" }, + { .name = "posix_slash", .kind = SP_FS_PATH_POSIX, .input = "A/B", .expect = "B" }, +}; + +sp_test_each(fs, get_name_for, test_t, tests) { + sp_expect_str_eq_c(t, sp_fs_get_name_for(sp_cstr_as_str(it->input), it->kind), it->expect); + return SP_OK; +} diff --git a/test/fs/remove/os.c b/test/fs/remove/os.c index 57c918c9..41fdc79b 100644 --- a/test/fs/remove/os.c +++ b/test/fs/remove/os.c @@ -4,14 +4,18 @@ #define DEEP_NAME "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" \ "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" +typedef struct { + sp_err_t err; + fs_expected_path_t paths [FS_MAX_PATHS]; +} expect_t; + typedef struct { const c8* name; fs_setup_t setup [FS_MAX_SETUP]; bool dir; const c8* cwd; const c8* path; - sp_err_t err; - fs_expected_path_t expect [FS_MAX_PATHS]; + expect_t expect; } test_t; static const test_t tests [] = { @@ -22,13 +26,17 @@ static const test_t tests [] = { }, .path = "A", .expect = { - { .path = "A" }, + .paths = { + { .path = "A" }, + }, }, }, { .name = "file_missing", .path = "A", - .err = SP_ERR_SYS_NOT_FOUND, + .expect = { + .err = SP_ERR_SYS_NOT_FOUND, + }, }, { .name = "file_is_dir", @@ -36,9 +44,11 @@ static const test_t tests [] = { { "A", FS_SETUP_DIR }, }, .path = "A", - .err = SP_ERR_SYS_IS_DIR, .expect = { - { .path = "A", .exists = true, .kind = SP_FS_KIND_DIR }, + .err = SP_ERR_SYS_IS_DIR, + .paths = { + { .path = "A", .exists = true, .kind = SP_FS_KIND_DIR }, + }, }, }, { @@ -53,30 +63,51 @@ static const test_t tests [] = { .dir = true, .path = "A", .expect = { - { .path = "A" }, - { .path = "A/B" }, - { .path = "A/C" }, - { .path = "A/C/D" }, - { .path = "A/E" }, + .paths = { + { .path = "A" }, + { .path = "A/B" }, + { .path = "A/C" }, + { .path = "A/C/D" }, + { .path = "A/E" }, + }, }, }, { - .name = "dir_nested_sub", + .name = "dir_multi_component_sub", .setup = { { "A/B/C" }, }, .dir = true, .path = "A/B", .expect = { - { .path = "A", .exists = true, .kind = SP_FS_KIND_DIR }, - { .path = "A/B" }, + .paths = { + { .path = "A", .exists = true, .kind = SP_FS_KIND_DIR }, + { .path = "A/B" }, + }, + }, + }, + { + .name = "dir_unicode_child_round_trips", + .setup = { + { "\xc3\xa4\x62\x63", FS_SETUP_DIR }, + { "\xc3\xa4\x62\x63/\xc3\xbc\x66\x69\x6c\x65.txt" }, + }, + .dir = true, + .path = "\xc3\xa4\x62\x63", + .expect = { + .paths = { + { .path = "\xc3\xa4\x62\x63" }, + { .path = "\xc3\xa4\x62\x63/\xc3\xbc\x66\x69\x6c\x65.txt" }, + }, }, }, { .name = "dir_missing", .dir = true, .path = "A", - .err = SP_ERR_SYS_NOT_FOUND, + .expect = { + .err = SP_ERR_SYS_NOT_FOUND, + }, }, { .name = "dir_is_file", @@ -85,9 +116,11 @@ static const test_t tests [] = { }, .dir = true, .path = "A", - .err = SP_ERR_SYS_NOT_DIR, .expect = { - { .path = "A", .exists = true, .kind = SP_FS_KIND_FILE }, + .err = SP_ERR_SYS_NOT_DIR, + .paths = { + { .path = "A", .exists = true, .kind = SP_FS_KIND_FILE }, + }, }, }, { @@ -98,9 +131,11 @@ static const test_t tests [] = { .dir = true, .cwd = "A", .path = ".", - .err = SP_ERR_SYS_INVALID, .expect = { - { .path = "A/B", .exists = true, .kind = SP_FS_KIND_FILE }, + .err = SP_ERR_SYS_INVALID, + .paths = { + { .path = "A/B", .exists = true, .kind = SP_FS_KIND_FILE }, + }, }, }, { @@ -110,11 +145,29 @@ static const test_t tests [] = { }, .dir = true, .path = "A/..", - .err = SP_ERR_SYS_INVALID, .expect = { - { .path = "A/B", .exists = true, .kind = SP_FS_KIND_FILE }, + .err = SP_ERR_SYS_INVALID, + .paths = { + { .path = "A/B", .exists = true, .kind = SP_FS_KIND_FILE }, + }, }, }, +#if defined(SP_WIN32) + { + .name = "dir_dotdot_backslash_rejected", + .setup = { + { "A/B" }, + }, + .dir = true, + .path = "A\\..", + .expect = { + .err = SP_ERR_SYS_INVALID, + .paths = { + { .path = "A/B", .exists = true, .kind = SP_FS_KIND_FILE }, + }, + }, + }, +#endif { .name = "dir_empty_rejected", .setup = { @@ -123,26 +176,32 @@ static const test_t tests [] = { .dir = true, .cwd = "A", .path = "", - .err = SP_ERR_SYS_INVALID, .expect = { - { .path = "A/B", .exists = true, .kind = SP_FS_KIND_FILE }, + .err = SP_ERR_SYS_INVALID, + .paths = { + { .path = "A/B", .exists = true, .kind = SP_FS_KIND_FILE }, + }, }, }, { .name = "dir_does_not_follow_symlink", .setup = { - { "B" }, + { "B", FS_SETUP_DIR }, + { "B/C" }, { "A", FS_SETUP_DIR }, { .path = "A/L", .kind = FS_SETUP_SYMLINK, .target = "B" }, - { "A/C" }, + { "A/D" }, }, .dir = true, .path = "A", .expect = { - { .path = "A" }, - { .path = "A/L" }, - { .path = "A/C" }, - { .path = "B", .exists = true, .kind = SP_FS_KIND_FILE }, + .paths = { + { .path = "A" }, + { .path = "A/L" }, + { .path = "A/D" }, + { .path = "B", .exists = true, .kind = SP_FS_KIND_DIR }, + { .path = "B/C", .exists = true, .kind = SP_FS_KIND_FILE }, + }, }, }, { @@ -155,9 +214,11 @@ static const test_t tests [] = { .dir = true, .path = "L", .expect = { - { .path = "L" }, - { .path = "A", .exists = true, .kind = SP_FS_KIND_DIR }, - { .path = "A/B", .exists = true, .kind = SP_FS_KIND_FILE }, + .paths = { + { .path = "L" }, + { .path = "A", .exists = true, .kind = SP_FS_KIND_DIR }, + { .path = "A/B", .exists = true, .kind = SP_FS_KIND_FILE }, + }, }, }, { @@ -170,9 +231,11 @@ static const test_t tests [] = { .dir = true, .path = "L/", .expect = { - { .path = "L" }, - { .path = "A", .exists = true, .kind = SP_FS_KIND_DIR }, - { .path = "A/B", .exists = true, .kind = SP_FS_KIND_FILE }, + .paths = { + { .path = "L" }, + { .path = "A", .exists = true, .kind = SP_FS_KIND_DIR }, + { .path = "A/B", .exists = true, .kind = SP_FS_KIND_FILE }, + }, }, }, #if defined(SP_POSIX) @@ -186,9 +249,11 @@ static const test_t tests [] = { .dir = true, .path = "A", .expect = { - { .path = "A" }, - { .path = "A/F" }, - { .path = "A/B" }, + .paths = { + { .path = "A" }, + { .path = "A/F" }, + { .path = "A/B" }, + }, }, }, #endif @@ -207,13 +272,13 @@ sp_test_each(fs, remove, test_t, tests) { sp_path_t path = { .dir = dir, .sub = sp_cstr_as_str(it->path) }; sp_err_t result = it->dir ? sp_fs_remove_dir_at(path) : sp_fs_remove_file_at(path); - sp_expect_err_eq(t, result, it->err); + sp_expect_err_eq(t, result, it->expect.err); sp_sys_file_meta_t meta = sp_zero; sp_expect_ok(t, sp_sys_get_file_metadata(dir, &meta)); sp_sys_close(dir); - fs_expect_paths(t, sandbox, it->expect); + fs_expect_paths(t, sandbox, it->expect.paths); return SP_OK; } @@ -246,24 +311,21 @@ sp_test(fs, remove_dir_deeper_than_path_max) { #if defined(SP_POSIX) sp_test(fs, remove_dir_unwritable_subdir_fails) { - sp_mem_t mem = sp_test_arena(t); + sp_sys_fd_t root = sp_sys_get_root(0); sp_str_t locked = fs_path_c(t, "A/B"); sp_must_ok(t, sp_fs_create_dir(locked)); sp_must_ok(t, sp_fs_create_file(fs_path_c(t, "A/B/C"))); - - const c8* locked_c = sp_cstr_from_str(mem, locked); - sp_must_eq(t, chmod(locked_c, 0555), 0); + sp_must_ok(t, sp_sys_set_file_perms_s(root, locked, (sp_sys_file_perms_t) { .value = 0555 })); sp_sys_fd_t dir = SP_SYS_INVALID_FD; - sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sp_test_dir(t), 0, &dir)); + sp_must_ok(t, sp_sys_open_dir_s(root, sp_test_dir(t), 0, &dir)); sp_err_t result = sp_fs_remove_dir_at((sp_path_t) { .dir = dir, .sub = sp_str_lit("A") }); sp_sys_close(dir); - sp_must_eq(t, chmod(locked_c, 0755), 0); if (!result) return sp_test_skip(t, "directory permissions not enforced"); + sp_must_ok(t, sp_sys_set_file_perms_s(root, locked, (sp_sys_file_perms_t) { .value = 0755 })); sp_expect_err_eq(t, result, SP_ERR_SYS_ACCESS_DENIED); fs_expect_paths(t, sp_test_dir(t), (const fs_expected_path_t [FS_MAX_PATHS]) { - { .path = "A", .exists = true, .kind = SP_FS_KIND_DIR }, { .path = "A/B", .exists = true, .kind = SP_FS_KIND_DIR }, { .path = "A/B/C", .exists = true, .kind = SP_FS_KIND_FILE }, }); diff --git a/test/fs/remove/sim.c b/test/fs/remove/sim.c index b87c311e..55149ae8 100644 --- a/test/fs/remove/sim.c +++ b/test/fs/remove/sim.c @@ -1,15 +1,17 @@ -#include "fs.h" +#include "sp.h" +#include "sp/sp_test.h" #include "sim.h" typedef struct { const c8* path; - sp_fs_kind_t kind; + sim_op_t op; } removed_t; typedef struct { sp_err_t err; removed_t removed [SIM_MAX_REMOVED]; u32 opens; + u32 rmdirs; } expect_t; typedef struct { @@ -25,8 +27,21 @@ static const test_t tests [] = { { .path = "T", .entries = { { "B", SP_FS_KIND_FILE, .unlink = SP_ERR_SYS_NOT_FOUND }, { "C", SP_FS_KIND_FILE } } }, }, .expect = { - .removed = { { "T/C", SP_FS_KIND_FILE }, { "T", SP_FS_KIND_DIR } }, + .removed = { { "T/C", SIM_OP_UNLINK }, { "T", SIM_OP_RMDIR } }, .opens = 1, + .rmdirs = 1, + }, + }, + { + .name = "dir_vanished_before_open_is_ok", + .dirs = { + { .path = "T", .entries = { { "B", SP_FS_KIND_DIR } } }, + { .path = "T/B", .open = SP_ERR_SYS_NOT_FOUND }, + }, + .expect = { + .removed = { { "T", SIM_OP_RMDIR } }, + .opens = 2, + .rmdirs = 1, }, }, { @@ -35,8 +50,9 @@ static const test_t tests [] = { { .path = "T", .rmdir = SP_ERR_SYS_NOT_FOUND, .entries = { { "B", SP_FS_KIND_FILE } } }, }, .expect = { - .removed = { { "T/B", SP_FS_KIND_FILE } }, + .removed = { { "T/B", SIM_OP_UNLINK } }, .opens = 1, + .rmdirs = 1, }, }, { @@ -46,8 +62,9 @@ static const test_t tests [] = { { .path = "T/B", .entries = { { "C", SP_FS_KIND_FILE } } }, }, .expect = { - .removed = { { "T/B/C", SP_FS_KIND_FILE }, { "T/B", SP_FS_KIND_DIR }, { "T", SP_FS_KIND_DIR } }, + .removed = { { "T/B/C", SIM_OP_UNLINK }, { "T/B", SIM_OP_RMDIR }, { "T", SIM_OP_RMDIR } }, .opens = 2, + .rmdirs = 2, }, }, { @@ -57,8 +74,21 @@ static const test_t tests [] = { { .path = "T/B", .open = SP_ERR_SYS_NOT_DIR }, }, .expect = { - .removed = { { "T/B", SP_FS_KIND_FILE }, { "T", SP_FS_KIND_DIR } }, + .removed = { { "T/B", SIM_OP_UNLINK }, { "T", SIM_OP_RMDIR } }, + .opens = 2, + .rmdirs = 1, + }, + }, + { + .name = "dir_hint_that_is_symlink_is_unlinked", + .dirs = { + { .path = "T", .entries = { { "B", SP_FS_KIND_DIR } } }, + { .path = "T/B", .open = SP_ERR_SYS_LOOP }, + }, + .expect = { + .removed = { { "T/B", SIM_OP_UNLINK }, { "T", SIM_OP_RMDIR } }, .opens = 2, + .rmdirs = 1, }, }, { @@ -69,10 +99,11 @@ static const test_t tests [] = { .expect = { .err = SP_ERR_SYS_NOT_EMPTY, .opens = 1, + .rmdirs = 1, }, }, { - .name = "read_error_unwinds", + .name = "read_error_fails_fast_and_unwinds", .dirs = { { .path = "T", .entries = { { "B", SP_FS_KIND_DIR }, { "C", SP_FS_KIND_FILE } } }, { .path = "T/B", .read = SP_ERR_SYS_IO }, @@ -92,19 +123,21 @@ sp_test_each(fs, remove_sim, test_t, tests, .serial = true) { sp_expect_err_eq(t, err, it->expect.err); - fs_match_t matches [SIM_MAX_REMOVED] = sp_zero; - u32 n = 0; - sp_carr_for(it->expect.removed, i) { - const removed_t* want = &it->expect.removed[i]; - if (!want->path) break; - matches[n++] = (fs_match_t) { .key = sp_cstr_as_str(want->path), .kind = want->kind }; + u32 expected = 0; + sp_carr_for(it->expect.removed, e) { + if (!it->expect.removed[e].path) break; + expected++; } - sp_for(i, s.num_removed) { - fs_match(t, matches, n, sp_cstr_as_str(s.removed[i].path), s.removed[i].kind); + sp_expect_eq(t, s.num_removed, expected); + sp_for(i, sp_min(s.num_removed, expected)) { + sp_expect_str_eq_c(t, sp_cstr_as_str(s.removed[i].path), it->expect.removed[i].path); + sp_expect_eq(t, (u32)s.removed[i].op, (u32)it->expect.removed[i].op); } - fs_match_finish(t, matches, n); sp_expect_eq(t, s.count.opens, it->expect.opens); + sp_expect_eq(t, s.count.nofollow, s.count.opens); + sp_expect_eq(t, s.count.rmdirs, it->expect.rmdirs); sp_expect_eq(t, s.count.closes, s.count.dirs); + sp_expect_eq(t, s.count.fd_closes, (u32)0); return SP_OK; } diff --git a/test/fs/sim.c b/test/fs/sim.c index f7347ea5..bf4faced 100644 --- a/test/fs/sim.c +++ b/test/fs/sim.c @@ -35,30 +35,31 @@ static u32 dir_len(const sim_dir_t* dir) { return n; } -static sp_str_t resolve(sp_sys_fd_t fd, const c8* path, u32 len, c8 buf [SP_PATH_MAX]) { - s64 slot = (s64)fd - FD_BASE; - if (slot < 0 || slot >= (s64)active->count.dirs) return sp_str(path, len); +static s64 slot_of(sp_sys_fd_t fd) { + return (s64)fd - FD_BASE; +} - sp_str_t base = sp_cstr_as_str(active->opened[slot].dir->path); - sp_assert(base.len + 1 + len < SP_PATH_MAX); - sp_str_copy_to(base, buf, SP_PATH_MAX); - buf[base.len] = '/'; - sp_mem_copy(buf + base.len + 1, path, len); - return sp_str(buf, base.len + 1 + len); +static sp_str_t resolve(sp_mem_t mem, sp_sys_fd_t fd, const c8* path, u32 len) { + if (fd == sp_sys_get_root(0)) return sp_str(path, len); + s64 slot = slot_of(fd); + sp_assert(slot >= 0 && slot < (s64)active->count.dirs); + return sp_fs_join_path(mem, sp_cstr_as_str(active->opened[slot].dir->path), sp_str(path, len)); } -static void record(sp_str_t path, sp_fs_kind_t kind) { +static void record(sp_str_t path, sim_op_t op) { sp_assert(active->num_removed < SIM_MAX_REMOVED); sp_assert(path.len < SIM_MAX_REMOVED_PATH); sim_removed_t* out = &active->removed[active->num_removed++]; sp_str_copy_to(path, out->path, SIM_MAX_REMOVED_PATH); - out->kind = kind; + out->op = op; } static sp_err_t open_dir(sp_sys_fd_t fd, const c8* path, u32 len, u32 flags, sp_sys_fd_t* out) { active->count.opens++; - c8 buf [SP_PATH_MAX]; - const sim_dir_t* dir = find_dir(resolve(fd, path, len, buf)); + if (flags & SP_SYS_OPEN_DIR_NO_FOLLOW) active->count.nofollow++; + sp_mem_arena_marker_t s = sp_mem_begin_scratch(); + const sim_dir_t* dir = find_dir(resolve(s.mem, fd, path, len)); + sp_mem_end_scratch(s); if (!dir) return SP_ERR_SYS_NOT_FOUND; if (dir->open) return dir->open; sp_assert(active->count.dirs < SIM_MAX_OPENS); @@ -74,7 +75,7 @@ static sp_err_t close_fd(sp_sys_fd_t fd) { } static sp_err_t dir_it_open(sp_sys_fd_t fd, sp_sys_dir_it_t* out) { - s64 slot = (s64)fd - FD_BASE; + s64 slot = slot_of(fd); if (active->opened[slot].dir->it_open) return active->opened[slot].dir->it_open; *out = sp_zero_s(sp_sys_dir_it_t); out->fd = fd; @@ -94,9 +95,13 @@ static sp_err_t dir_it_parse(sp_sys_dir_it_t* it, sp_mem_buffer_t* buf, u64* cur const sim_entry_t* entry = &active->opened[it->state].dir->entries[*cursor]; *cursor += 1; + u32 len = (u32)sp_cstr_len(entry->name); + sp_assert(len <= buf->capacity); + sp_mem_copy(buf->data, entry->name, len); + *out = sp_zero_s(sp_sys_dir_entry_t); - out->name = entry->name; - out->len = (u32)sp_cstr_len(entry->name); + out->name = sp_ptr_cast(const c8*, buf->data); + out->len = len; out->kind = entry->kind; return SP_OK; } @@ -107,40 +112,46 @@ static sp_err_t dir_it_close(sp_sys_dir_it_t* it) { } static sp_err_t get_link_metadata(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_file_meta_t* st) { - c8 buf [SP_PATH_MAX]; - sp_str_t full = resolve(fd, path, len, buf); - *st = sp_zero_s(sp_sys_file_meta_t); - + sp_mem_arena_marker_t s = sp_mem_begin_scratch(); + sp_str_t full = resolve(s.mem, fd, path, len); const sim_entry_t* entry = find_entry(full); - if (entry) { + const sim_dir_t* dir = find_dir(full); + sp_mem_end_scratch(s); + + *st = sp_zero_s(sp_sys_file_meta_t); + if (entry && entry->stat != SP_FS_KIND_NONE) { st->kind = entry->stat; return SP_OK; } - if (find_dir(full)) { + if (dir) { st->kind = SP_FS_KIND_DIR; return SP_OK; } + if (entry) return SP_OK; return SP_ERR_SYS_NOT_FOUND; } static sp_err_t unlink_entry(sp_sys_fd_t fd, const c8* path, u32 len) { - c8 buf [SP_PATH_MAX]; - sp_str_t full = resolve(fd, path, len, buf); + sp_mem_arena_marker_t s = sp_mem_begin_scratch(); + sp_str_t full = resolve(s.mem, fd, path, len); const sim_entry_t* entry = find_entry(full); - if (!entry) return SP_ERR_SYS_NOT_FOUND; - if (entry->unlink) return entry->unlink; - record(full, SP_FS_KIND_FILE); - return SP_OK; + sp_err_t err = SP_ERR_SYS_NOT_FOUND; + if (entry) err = entry->unlink; + if (entry && !err) record(full, SIM_OP_UNLINK); + sp_mem_end_scratch(s); + return err; } static sp_err_t remove_dir(sp_sys_fd_t fd, const c8* path, u32 len) { - c8 buf [SP_PATH_MAX]; - sp_str_t full = resolve(fd, path, len, buf); + active->count.rmdirs++; + sp_mem_arena_marker_t s = sp_mem_begin_scratch(); + sp_str_t full = resolve(s.mem, fd, path, len); const sim_dir_t* dir = find_dir(full); - if (!dir) return SP_ERR_SYS_NOT_FOUND; - if (dir->rmdir) return dir->rmdir; - record(full, SP_FS_KIND_DIR); - return SP_OK; + sp_err_t err = SP_ERR_SYS_NOT_FOUND; + if (dir) err = dir->rmdir; + if (dir && !err) record(full, SIM_OP_RMDIR); + sp_mem_end_scratch(s); + return err; } void sim_begin(sim_t* sim, const sim_dir_t* dirs) { diff --git a/test/fs/sim.h b/test/fs/sim.h index 01b45598..0be9e894 100644 --- a/test/fs/sim.h +++ b/test/fs/sim.h @@ -9,6 +9,11 @@ #define SIM_MAX_REMOVED 8 #define SIM_MAX_REMOVED_PATH 64 +typedef enum { + SIM_OP_UNLINK, + SIM_OP_RMDIR, +} sim_op_t; + typedef struct { const c8* name; sp_fs_kind_t kind; @@ -35,11 +40,13 @@ typedef struct { u32 dirs; u32 closes; u32 fd_closes; + u32 rmdirs; + u32 nofollow; } sim_count_t; typedef struct { c8 path [SIM_MAX_REMOVED_PATH]; - sp_fs_kind_t kind; + sim_op_t op; } sim_removed_t; typedef struct { From 3208d31596f8bcda6e22c50de25760491f86fa1b Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Wed, 23 Sep 2026 19:48:15 -0400 Subject: [PATCH 20/72] fs: walker enter/leave stubs --- sp.h | 82 ++++++++++---------- test/fs/it/enter.c | 184 ++++++++++++++++++++++++++++++++++++++++++++ test/fs/it/os.c | 6 +- test/fs/it/sim.c | 13 ++-- test/fs/remove/os.c | 15 +++- 5 files changed, 250 insertions(+), 50 deletions(-) create mode 100644 test/fs/it/enter.c diff --git a/sp.h b/sp.h index e6c98401..02deede9 100644 --- a/sp.h +++ b/sp.h @@ -3065,20 +3065,27 @@ typedef struct { u32 len; } sp_fs_it_frame_t; +typedef enum { + SP_FS_IT_ENTRY, + SP_FS_IT_LEAVE, +} sp_fs_it_yield_t; + typedef struct { sp_mem_t mem; sp_sys_fd_t dir; sp_fs_entry_t entry; + sp_path_t at; + sp_fs_it_yield_t yield; sp_da(sp_fs_it_frame_t) stack; c8 path [SP_PATH_MAX]; - bool recursive; + bool entered; sp_err_t err; } sp_fs_it_t; #define sp_fs_for(mem, dir, it) \ for (sp_fs_it_t it = sp_fs_it_new(mem, dir); sp_fs_it_next(&it);) #define sp_fs_for_recursive(mem, dir, it) \ - for (sp_fs_it_t it = sp_fs_it_new_recursive(mem, dir); sp_fs_it_next(&it);) + for (sp_fs_it_t it = sp_fs_it_new(mem, dir); sp_fs_it_walk(&it);) SP_API sp_str_t sp_fs_get_name(sp_str_t path); SP_API sp_str_t sp_fs_get_name_for(sp_str_t path, sp_fs_path_kind_t kind); @@ -3139,10 +3146,10 @@ SP_API sp_err_t sp_fs_dir_open(sp_fs_dir_t* it, sp_sys_fd_t fd, sp_s SP_API sp_err_t sp_fs_dir_next(sp_fs_dir_t* it, sp_fs_dir_entry_t* out); SP_API sp_err_t sp_fs_dir_close(sp_fs_dir_t* it); SP_API sp_fs_it_t sp_fs_it_new(sp_mem_t mem, sp_str_t path); -SP_API sp_fs_it_t sp_fs_it_new_at(sp_mem_t mem, sp_path_t path); -SP_API sp_fs_it_t sp_fs_it_new_recursive(sp_mem_t mem, sp_str_t path); -SP_API sp_fs_it_t sp_fs_it_new_recursive_at(sp_mem_t mem, sp_path_t path); +SP_API sp_fs_it_t sp_fs_it_new_at(sp_mem_t mem, sp_path_t path, u32 flags); SP_API bool sp_fs_it_next(sp_fs_it_t* it); +SP_API bool sp_fs_it_walk(sp_fs_it_t* it); +SP_API sp_err_t sp_fs_it_enter(sp_fs_it_t* it); SP_API void sp_fs_it_deinit(sp_fs_it_t* it); // literally sys+normalize, if normalize isnt needed this isn't either? @@ -20220,19 +20227,15 @@ SP_PRIVATE sp_err_t sp_fs_it_join(sp_fs_it_t* it, u32 prefix, sp_str_t name, u32 return SP_OK; } -SP_PRIVATE sp_err_t sp_fs_it_push(sp_fs_it_t* it, u32 len) { +SP_PRIVATE sp_err_t sp_fs_it_open(sp_fs_it_t* it, sp_str_t path, u32 flags) { + u32 len = 0; + sp_try(sp_fs_it_join(it, 0, sp_fs_trim_path(path), &len)); sp_fs_it_frame_t frame = { .len = len }; - sp_try(sp_fs_dir_open(&frame.dir, it->dir, sp_str(it->path, len), 0, sp_mem_slice(frame.buf, SP_FS_IT_BUF_SIZE))); + sp_try(sp_fs_dir_open(&frame.dir, it->dir, sp_str(it->path, len), flags, sp_mem_slice(frame.buf, SP_FS_IT_BUF_SIZE))); sp_da_push(it->stack, frame); return SP_OK; } -SP_PRIVATE sp_err_t sp_fs_it_open(sp_fs_it_t* it, sp_str_t path) { - u32 len = 0; - sp_try(sp_fs_it_join(it, 0, sp_fs_trim_path(path), &len)); - return sp_fs_it_push(it, len); -} - SP_PRIVATE sp_err_t sp_fs_it_step(sp_fs_it_t* it) { while (!sp_da_empty(it->stack)) { sp_fs_it_frame_t* top = sp_da_back(it->stack); @@ -20259,9 +20262,6 @@ SP_PRIVATE sp_err_t sp_fs_it_step(sp_fs_it_t* it) { it->entry.kind = sp_fs_get_link_kind_at((sp_path_t) { .dir = it->dir, .sub = it->entry.path }); } - if (it->recursive && it->entry.kind == SP_FS_KIND_DIR) { - sp_try(sp_fs_it_push(it, len)); - } return SP_OK; } return SP_OK; @@ -20275,6 +20275,14 @@ bool sp_fs_it_next(sp_fs_it_t* it) { return !it->err && !sp_da_empty(it->stack); } +bool sp_fs_it_walk(sp_fs_it_t* it) { + return false; +} + +sp_err_t sp_fs_it_enter(sp_fs_it_t* it) { + return SP_ERR_SYS_UNSUPPORTED; +} + void sp_fs_it_deinit(sp_fs_it_t* it) { sp_fs_it_unwind(it); sp_da_free(it->stack); @@ -20289,23 +20297,24 @@ SP_PRIVATE sp_fs_entry_t sp_fs_entry_copy(sp_mem_t mem, sp_fs_entry_t entry) { }; } -SP_PRIVATE sp_err_t sp_fs_collect_it(sp_mem_t mem, sp_fs_it_t* it, sp_da(sp_fs_entry_t)* out) { - *out = sp_da_new(mem, sp_fs_entry_t); - while (sp_fs_it_next(it)) { - sp_da_push(*out, sp_fs_entry_copy(mem, it->entry)); - } - sp_fs_it_deinit(it); - return it->err; -} - sp_err_t sp_fs_collect(sp_mem_t mem, sp_str_t path, sp_da(sp_fs_entry_t)* out) { + *out = sp_da_new(mem, sp_fs_entry_t); sp_fs_it_t it = sp_fs_it_new(mem, path); - return sp_fs_collect_it(mem, &it, out); + while (sp_fs_it_next(&it)) { + sp_da_push(*out, sp_fs_entry_copy(mem, it.entry)); + } + sp_fs_it_deinit(&it); + return it.err; } sp_err_t sp_fs_collect_recursive(sp_mem_t mem, sp_str_t path, sp_da(sp_fs_entry_t)* out) { - sp_fs_it_t it = sp_fs_it_new_recursive(mem, path); - return sp_fs_collect_it(mem, &it, out); + *out = sp_da_new(mem, sp_fs_entry_t); + sp_fs_it_t it = sp_fs_it_new(mem, path); + while (sp_fs_it_walk(&it)) { + sp_da_push(*out, sp_fs_entry_copy(mem, it.entry)); + } + sp_fs_it_deinit(&it); + return it.err; } typedef struct { @@ -20753,26 +20762,15 @@ sp_err_t sp_fs_copy_at(sp_path_t from, sp_path_t to, sp_fs_atomic_mode_t mode) { SP_UNREACHABLE_RETURN(SP_OK); } -sp_fs_it_t sp_fs_it_new_at(sp_mem_t mem, sp_path_t path) { +sp_fs_it_t sp_fs_it_new_at(sp_mem_t mem, sp_path_t path, u32 flags) { sp_fs_it_t it = { .mem = mem, .dir = path.dir }; sp_da_init(mem, it.stack); - it.err = sp_fs_it_open(&it, path.sub); + it.err = sp_fs_it_open(&it, path.sub, flags); return it; } sp_fs_it_t sp_fs_it_new(sp_mem_t mem, sp_str_t path) { - return sp_fs_it_new_at(mem, sp_path_at_root(path)); -} - -sp_fs_it_t sp_fs_it_new_recursive_at(sp_mem_t mem, sp_path_t path) { - sp_fs_it_t it = { .mem = mem, .dir = path.dir, .recursive = true }; - sp_da_init(mem, it.stack); - it.err = sp_fs_it_open(&it, path.sub); - return it; -} - -sp_fs_it_t sp_fs_it_new_recursive(sp_mem_t mem, sp_str_t path) { - return sp_fs_it_new_recursive_at(mem, sp_path_at_root(path)); + return sp_fs_it_new_at(mem, sp_path_at_root(path), 0); } // diff --git a/test/fs/it/enter.c b/test/fs/it/enter.c new file mode 100644 index 00000000..34f57036 --- /dev/null +++ b/test/fs/it/enter.c @@ -0,0 +1,184 @@ +#include "fs.h" + +typedef struct { + const c8* path; + sp_err_t err; + bool remove; +} enter_t; + +typedef struct { + const c8* path; + sp_fs_kind_t kind; +} entry_t; + +typedef struct { + entry_t entries [FS_MAX_PATHS]; + const c8* leaves [FS_MAX_PATHS]; +} expect_t; + +typedef struct { + const c8* name; + fs_setup_t setup [FS_MAX_SETUP]; + enter_t enter [FS_MAX_PATHS]; + expect_t expect; +} test_t; + +static const test_t tests [] = { + { + .name = "enters_only_listed", + .setup = { + { "R/A/B" }, + { "R/C/D" }, + { "R/E" }, + }, + .enter = { { "A" } }, + .expect = { + .entries = { + { "A", SP_FS_KIND_DIR }, + { "A/B", SP_FS_KIND_FILE }, + { "C", SP_FS_KIND_DIR }, + { "E", SP_FS_KIND_FILE }, + }, + .leaves = { "A" }, + }, + }, + { + .name = "leaves_in_stack_order", + .setup = { + { "R/A/B/C" }, + }, + .enter = { { "A" }, { "A/B" } }, + .expect = { + .entries = { + { "A", SP_FS_KIND_DIR }, + { "A/B", SP_FS_KIND_DIR }, + { "A/B/C", SP_FS_KIND_FILE }, + }, + .leaves = { "A/B", "A" }, + }, + }, + { + .name = "no_enter_no_leave", + .setup = { + { "R/A/B" }, + }, + .expect = { + .entries = { + { "A", SP_FS_KIND_DIR }, + }, + }, + }, + { + .name = "enter_file_is_not_dir", + .setup = { + { "R/A" }, + { "R/B" }, + }, + .enter = { { "A", SP_ERR_SYS_NOT_DIR } }, + .expect = { + .entries = { + { "A", SP_FS_KIND_FILE }, + { "B", SP_FS_KIND_FILE }, + }, + }, + }, + { + .name = "enter_symlink_is_loop", + .setup = { + { "R/A", FS_SETUP_DIR }, + { "R/A/B" }, + { .path = "R/L", .kind = FS_SETUP_SYMLINK, .target = "R/A" }, + }, + .enter = { { "L", SP_ERR_SYS_LOOP } }, + .expect = { + .entries = { + { "A", SP_FS_KIND_DIR }, + { "L", SP_FS_KIND_SYMLINK }, + }, + }, + }, + { + .name = "enter_vanished_is_not_found", + .setup = { + { "R/A/B" }, + { "R/C" }, + }, + .enter = { { "A", SP_ERR_SYS_NOT_FOUND, .remove = true } }, + .expect = { + .entries = { + { "A", SP_FS_KIND_DIR }, + { "C", SP_FS_KIND_FILE }, + }, + }, + }, +}; + +static const enter_t* find_enter(sp_mem_t mem, const test_t* test, sp_str_t base, sp_str_t path) { + sp_carr_for(test->enter, e) { + const enter_t* enter = &test->enter[e]; + if (!enter->path) break; + if (sp_str_equal(sp_fs_join_path(mem, base, sp_cstr_as_str(enter->path)), path)) return enter; + } + return SP_NULLPTR; +} + +sp_test_each(fs, it_enter, test_t, tests) { + skip_if_symlinks_needed(t, it->setup); + + sp_mem_t mem = sp_test_arena(t); + sp_str_t sandbox = sp_test_dir(t); + fs_apply_setup(t, sandbox, it->setup); + + sp_sys_fd_t dir = SP_SYS_INVALID_FD; + sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, 0, &dir)); + + sp_str_t base = sp_str_lit("R"); + fs_match_t entries [FS_MAX_PATHS] = sp_zero; + u32 num_entries = 0; + sp_carr_for(it->expect.entries, e) { + const entry_t* want = &it->expect.entries[e]; + if (!want->path) break; + entries[num_entries++] = (fs_match_t) { .key = sp_fs_join_path(mem, base, sp_cstr_as_str(want->path)), .kind = want->kind }; + } + + fs_match_t leaves [FS_MAX_PATHS] = sp_zero; + u32 num_leaves = 0; + sp_carr_for(it->expect.leaves, e) { + if (!it->expect.leaves[e]) break; + leaves[num_leaves++] = (fs_match_t) { .key = sp_fs_join_path(mem, base, sp_cstr_as_str(it->expect.leaves[e])), .kind = SP_FS_KIND_DIR }; + } + + sp_str_t entered [FS_MAX_PATHS] = sp_zero; + u32 depth = 0; + + sp_fs_it_t walk = sp_fs_it_new_at(mem, (sp_path_t) { .dir = dir, .sub = base }, 0); + while (sp_fs_it_next(&walk)) { + sp_expect_str_eq(t, walk.at.sub, walk.entry.name); + switch (walk.yield) { + case SP_FS_IT_LEAVE: { + fs_match(t, leaves, num_leaves, walk.entry.path, SP_FS_KIND_DIR); + sp_expect_eq(t, (u32)sp_fs_get_kind_at(walk.at), (u32)SP_FS_KIND_DIR); + sp_must(t, depth > 0); + sp_expect_str_eq(t, walk.entry.path, entered[--depth]); + break; + } + case SP_FS_IT_ENTRY: { + fs_match(t, entries, num_entries, walk.entry.path, walk.entry.kind); + const enter_t* enter = find_enter(mem, it, base, walk.entry.path); + if (!enter) break; + if (enter->remove) sp_must_ok(t, sp_fs_remove_dir_at(walk.at)); + sp_expect_err_eq(t, sp_fs_it_enter(&walk), enter->err); + if (!enter->err) entered[depth++] = sp_str_copy(mem, walk.entry.path); + break; + } + } + } + sp_expect_ok(t, walk.err); + sp_expect_eq(t, depth, (u32)0); + sp_fs_it_deinit(&walk); + sp_sys_close(dir); + + fs_match_finish(t, entries, num_entries); + fs_match_finish(t, leaves, num_leaves); + return SP_OK; +} diff --git a/test/fs/it/os.c b/test/fs/it/os.c index b8195f19..fe1835a1 100644 --- a/test/fs/it/os.c +++ b/test/fs/it/os.c @@ -181,9 +181,11 @@ sp_test_each(fs, it, test_t, tests) { matches[n++] = (fs_match_t) { .key = sp_fs_join_path(mem, base, sp_cstr_as_str(want->path)), .kind = want->kind }; } - sp_fs_it_t walk = it->recursive ? sp_fs_it_new_recursive_at(mem, root) : sp_fs_it_new_at(mem, root); - while (sp_fs_it_next(&walk)) { + sp_fs_it_t walk = sp_fs_it_new_at(mem, root, 0); + while (it->recursive ? sp_fs_it_walk(&walk) : sp_fs_it_next(&walk)) { sp_expect_str_eq(t, walk.entry.name, sp_fs_get_name(walk.entry.path)); + sp_expect_str_eq(t, walk.at.sub, walk.entry.name); + sp_expect_eq(t, (u32)sp_fs_get_kind_at(walk.at), (u32)walk.entry.kind); fs_match(t, matches, n, walk.entry.path, walk.entry.kind); } sp_expect_err_eq(t, walk.err, it->expect.err); diff --git a/test/fs/it/sim.c b/test/fs/it/sim.c index 74aba99e..649d9fe3 100644 --- a/test/fs/it/sim.c +++ b/test/fs/it/sim.c @@ -105,13 +105,16 @@ static const test_t tests [] = { .name = "deinit_closes_open_frames", .root = "T", .recursive = true, - .stop_after = 1, + .stop_after = 2, .dirs = { { .path = "T", .entries = { { "B", SP_FS_KIND_DIR } } }, - { .path = "T/B" }, + { .path = "T/B", .entries = { { "C", SP_FS_KIND_FILE }, { "D", SP_FS_KIND_FILE } } }, }, .expect = { - .entries = { { "T/B", "B", SP_FS_KIND_DIR } }, + .entries = { + { "T/B", "B", SP_FS_KIND_DIR }, + { "T/B/C", "C", SP_FS_KIND_FILE }, + }, .opens = 2, }, }, @@ -178,12 +181,12 @@ sp_test_each(fs, it_sim, test_t, tests, .serial = true) { sim_t s = sp_zero; sim_begin(&s, it->dirs); - sp_fs_it_t walk = it->recursive ? sp_fs_it_new_recursive(mem, root) : sp_fs_it_new(mem, root); + sp_fs_it_t walk = sp_fs_it_new(mem, root); sp_expect_err_eq(t, walk.err, it->expect.open); sp_expect(t, !walk.entry.path.data); u32 produced = 0; - while (sp_fs_it_next(&walk)) { + while (it->recursive ? sp_fs_it_walk(&walk) : sp_fs_it_next(&walk)) { if (produced < MAX_WALK && it->expect.entries[produced].path) { const entry_t* want = &it->expect.entries[produced]; sp_expect_str_eq_c(t, walk.entry.path, want->path); diff --git a/test/fs/remove/os.c b/test/fs/remove/os.c index 41fdc79b..16fe2b10 100644 --- a/test/fs/remove/os.c +++ b/test/fs/remove/os.c @@ -282,7 +282,7 @@ sp_test_each(fs, remove, test_t, tests) { return SP_OK; } -sp_test(fs, remove_dir_deeper_than_path_max) { +sp_test(fs, remove_dir_deeper_than_path_max_is_name_too_long) { sp_sys_fd_t dir = SP_SYS_INVALID_FD; sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sp_test_dir(t), 0, &dir)); @@ -303,6 +303,19 @@ sp_test(fs, remove_dir_deeper_than_path_max) { sp_must_ok(t, sp_fs_create_file_at((sp_path_t) { .dir = cur, .sub = sp_str_lit("F") })); sp_sys_close(cur); + sp_expect_err_eq(t, sp_fs_remove_dir_at(root), SP_ERR_SYS_NAME_TOO_LONG); + sp_expect(t, sp_fs_exists_at(root)); + + sp_must_ok(t, sp_sys_open_dir_s(root.dir, root.sub, 0, &cur)); + sp_for(level, DEEP_LEVELS / 2) { + sp_sys_fd_t next = SP_SYS_INVALID_FD; + sp_must_ok(t, sp_sys_open_dir_s(cur, sp_str_lit(DEEP_NAME), 0, &next)); + sp_sys_close(cur); + cur = next; + } + sp_expect_ok(t, sp_fs_remove_dir_at((sp_path_t) { .dir = cur, .sub = sp_str_lit(DEEP_NAME) })); + sp_sys_close(cur); + sp_expect_ok(t, sp_fs_remove_dir_at(root)); sp_expect(t, !sp_fs_exists_at(root)); sp_sys_close(dir); From cafba644aa11585b197c120d55e7193f079c76b5 Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Wed, 23 Sep 2026 20:07:17 -0400 Subject: [PATCH 21/72] fs: walker design review --- sp.h | 15 ++-- test/fs/it/enter.c | 37 +++------- test/fs/it/enter_sim.c | 152 +++++++++++++++++++++++++++++++++++++++++ test/fs/it/sim.c | 28 ++++++-- 4 files changed, 189 insertions(+), 43 deletions(-) create mode 100644 test/fs/it/enter_sim.c diff --git a/sp.h b/sp.h index 02deede9..a315cb5c 100644 --- a/sp.h +++ b/sp.h @@ -3071,14 +3071,11 @@ typedef enum { } sp_fs_it_yield_t; typedef struct { - sp_mem_t mem; - sp_sys_fd_t dir; sp_fs_entry_t entry; sp_path_t at; sp_fs_it_yield_t yield; sp_da(sp_fs_it_frame_t) stack; c8 path [SP_PATH_MAX]; - bool entered; sp_err_t err; } sp_fs_it_t; @@ -20227,11 +20224,11 @@ SP_PRIVATE sp_err_t sp_fs_it_join(sp_fs_it_t* it, u32 prefix, sp_str_t name, u32 return SP_OK; } -SP_PRIVATE sp_err_t sp_fs_it_open(sp_fs_it_t* it, sp_str_t path, u32 flags) { +SP_PRIVATE sp_err_t sp_fs_it_open(sp_fs_it_t* it, sp_path_t path, u32 flags) { u32 len = 0; - sp_try(sp_fs_it_join(it, 0, sp_fs_trim_path(path), &len)); + sp_try(sp_fs_it_join(it, 0, sp_fs_trim_path(path.sub), &len)); sp_fs_it_frame_t frame = { .len = len }; - sp_try(sp_fs_dir_open(&frame.dir, it->dir, sp_str(it->path, len), flags, sp_mem_slice(frame.buf, SP_FS_IT_BUF_SIZE))); + sp_try(sp_fs_dir_open(&frame.dir, path.dir, sp_str(it->path, len), flags, sp_mem_slice(frame.buf, SP_FS_IT_BUF_SIZE))); sp_da_push(it->stack, frame); return SP_OK; } @@ -20259,7 +20256,7 @@ SP_PRIVATE sp_err_t sp_fs_it_step(sp_fs_it_t* it) { // If the OS didn't give us a hint, specifically get it from metadata if (it->entry.kind == SP_FS_KIND_NONE) { - it->entry.kind = sp_fs_get_link_kind_at((sp_path_t) { .dir = it->dir, .sub = it->entry.path }); + it->entry.kind = sp_fs_get_link_kind_at((sp_path_t) { .dir = top->dir.dir.fd, .sub = it->entry.name }); } return SP_OK; @@ -20763,9 +20760,9 @@ sp_err_t sp_fs_copy_at(sp_path_t from, sp_path_t to, sp_fs_atomic_mode_t mode) { } sp_fs_it_t sp_fs_it_new_at(sp_mem_t mem, sp_path_t path, u32 flags) { - sp_fs_it_t it = { .mem = mem, .dir = path.dir }; + sp_fs_it_t it = sp_zero; sp_da_init(mem, it.stack); - it.err = sp_fs_it_open(&it, path.sub, flags); + it.err = sp_fs_it_open(&it, path, flags); return it; } diff --git a/test/fs/it/enter.c b/test/fs/it/enter.c index 34f57036..0b7102a9 100644 --- a/test/fs/it/enter.c +++ b/test/fs/it/enter.c @@ -3,7 +3,7 @@ typedef struct { const c8* path; sp_err_t err; - bool remove; + bool vanish; } enter_t; typedef struct { @@ -13,7 +13,6 @@ typedef struct { typedef struct { entry_t entries [FS_MAX_PATHS]; - const c8* leaves [FS_MAX_PATHS]; } expect_t; typedef struct { @@ -39,11 +38,10 @@ static const test_t tests [] = { { "C", SP_FS_KIND_DIR }, { "E", SP_FS_KIND_FILE }, }, - .leaves = { "A" }, }, }, { - .name = "leaves_in_stack_order", + .name = "nested_enters", .setup = { { "R/A/B/C" }, }, @@ -54,18 +52,6 @@ static const test_t tests [] = { { "A/B", SP_FS_KIND_DIR }, { "A/B/C", SP_FS_KIND_FILE }, }, - .leaves = { "A/B", "A" }, - }, - }, - { - .name = "no_enter_no_leave", - .setup = { - { "R/A/B" }, - }, - .expect = { - .entries = { - { "A", SP_FS_KIND_DIR }, - }, }, }, { @@ -100,10 +86,10 @@ static const test_t tests [] = { { .name = "enter_vanished_is_not_found", .setup = { - { "R/A/B" }, + { "R/A", FS_SETUP_DIR }, { "R/C" }, }, - .enter = { { "A", SP_ERR_SYS_NOT_FOUND, .remove = true } }, + .enter = { { "A", SP_ERR_SYS_NOT_FOUND, .vanish = true } }, .expect = { .entries = { { "A", SP_FS_KIND_DIR }, @@ -143,20 +129,14 @@ sp_test_each(fs, it_enter, test_t, tests) { fs_match_t leaves [FS_MAX_PATHS] = sp_zero; u32 num_leaves = 0; - sp_carr_for(it->expect.leaves, e) { - if (!it->expect.leaves[e]) break; - leaves[num_leaves++] = (fs_match_t) { .key = sp_fs_join_path(mem, base, sp_cstr_as_str(it->expect.leaves[e])), .kind = SP_FS_KIND_DIR }; - } - sp_str_t entered [FS_MAX_PATHS] = sp_zero; u32 depth = 0; sp_fs_it_t walk = sp_fs_it_new_at(mem, (sp_path_t) { .dir = dir, .sub = base }, 0); while (sp_fs_it_next(&walk)) { - sp_expect_str_eq(t, walk.at.sub, walk.entry.name); switch (walk.yield) { case SP_FS_IT_LEAVE: { - fs_match(t, leaves, num_leaves, walk.entry.path, SP_FS_KIND_DIR); + fs_match(t, leaves, num_leaves, walk.entry.path, walk.entry.kind); sp_expect_eq(t, (u32)sp_fs_get_kind_at(walk.at), (u32)SP_FS_KIND_DIR); sp_must(t, depth > 0); sp_expect_str_eq(t, walk.entry.path, entered[--depth]); @@ -166,9 +146,12 @@ sp_test_each(fs, it_enter, test_t, tests) { fs_match(t, entries, num_entries, walk.entry.path, walk.entry.kind); const enter_t* enter = find_enter(mem, it, base, walk.entry.path); if (!enter) break; - if (enter->remove) sp_must_ok(t, sp_fs_remove_dir_at(walk.at)); + if (enter->vanish) sp_must_ok(t, sp_sys_rmdir_s(walk.at.dir, walk.at.sub)); sp_expect_err_eq(t, sp_fs_it_enter(&walk), enter->err); - if (!enter->err) entered[depth++] = sp_str_copy(mem, walk.entry.path); + if (enter->err) break; + sp_str_t path = sp_str_copy(mem, walk.entry.path); + entered[depth++] = path; + leaves[num_leaves++] = (fs_match_t) { .key = path, .kind = SP_FS_KIND_DIR }; break; } } diff --git a/test/fs/it/enter_sim.c b/test/fs/it/enter_sim.c new file mode 100644 index 00000000..f480e7e1 --- /dev/null +++ b/test/fs/it/enter_sim.c @@ -0,0 +1,152 @@ +#include "sp.h" +#include "sp/sp_test.h" +#include "sim.h" + +#define MAX_YIELDS 6 + +typedef struct { + const c8* path; + sp_err_t err; +} enter_t; + +typedef struct { + const c8* path; + sp_fs_kind_t kind; + sp_fs_it_yield_t yield; +} yield_t; + +typedef struct { + yield_t yields [MAX_YIELDS]; + u32 opens; +} expect_t; + +typedef struct { + const c8* name; + sim_dir_t dirs [SIM_MAX_DIRS]; + enter_t enter [MAX_YIELDS]; + expect_t expect; +} test_t; + +static const test_t tests [] = { + { + .name = "leaves_after_children_before_siblings", + .dirs = { + { .path = "T", .entries = { { "B", SP_FS_KIND_DIR }, { "C", SP_FS_KIND_FILE } } }, + { .path = "T/B", .entries = { { "D", SP_FS_KIND_FILE } } }, + }, + .enter = { { "T/B" } }, + .expect = { + .yields = { + { "T/B", SP_FS_KIND_DIR }, + { "T/B/D", SP_FS_KIND_FILE }, + { "T/B", SP_FS_KIND_DIR, SP_FS_IT_LEAVE }, + { "T/C", SP_FS_KIND_FILE }, + }, + .opens = 2, + }, + }, + { + .name = "pruned_dir_is_never_opened", + .dirs = { + { .path = "T", .entries = { { "B", SP_FS_KIND_DIR } } }, + { .path = "T/B", .entries = { { "C", SP_FS_KIND_FILE } } }, + }, + .expect = { + .yields = { + { "T/B", SP_FS_KIND_DIR }, + }, + .opens = 1, + }, + }, + { + .name = "enter_ignores_kind_hint", + .dirs = { + { .path = "T", .entries = { { "B", SP_FS_KIND_FILE } } }, + { .path = "T/B", .entries = { { "C", SP_FS_KIND_FILE } } }, + }, + .enter = { { "T/B" } }, + .expect = { + .yields = { + { "T/B", SP_FS_KIND_FILE }, + { "T/B/C", SP_FS_KIND_FILE }, + { "T/B", SP_FS_KIND_DIR, SP_FS_IT_LEAVE }, + }, + .opens = 2, + }, + }, + { + .name = "enter_error_is_returned_and_walk_continues", + .dirs = { + { .path = "T", .entries = { { "B", SP_FS_KIND_DIR }, { "C", SP_FS_KIND_FILE } } }, + { .path = "T/B", .open = SP_ERR_SYS_IO }, + }, + .enter = { { "T/B", SP_ERR_SYS_IO } }, + .expect = { + .yields = { + { "T/B", SP_FS_KIND_DIR }, + { "T/C", SP_FS_KIND_FILE }, + }, + .opens = 2, + }, + }, +}; + +static const enter_t* find_enter(const test_t* test, sp_str_t path) { + sp_carr_for(test->enter, e) { + const enter_t* enter = &test->enter[e]; + if (!enter->path) break; + if (sp_str_equal(sp_cstr_as_str(enter->path), path)) return enter; + } + return SP_NULLPTR; +} + +sp_test_each(fs, it_enter_sim, test_t, tests, .serial = true) { + sp_mem_t mem = sp_test_arena(t); + + sim_t s = sp_zero; + sim_begin(&s, it->dirs); + + u32 leaves = 0; + u32 produced = 0; + sp_fs_it_t walk = sp_fs_it_new_at(mem, (sp_path_t) { .dir = sp_sys_get_root(0), .sub = sp_str_lit("T") }, 0); + while (sp_fs_it_next(&walk)) { + if (produced < MAX_YIELDS && it->expect.yields[produced].path) { + const yield_t* want = &it->expect.yields[produced]; + sp_expect_str_eq_c(t, walk.entry.path, want->path); + sp_expect_eq(t, (u32)walk.entry.kind, (u32)want->kind); + sp_expect_eq(t, (u32)walk.yield, (u32)want->yield); + } + else { + sp_test_fail(t, "walker produced unexpected yield {}", sp_fmt_str(walk.entry.path)); + } + produced++; + + switch (walk.yield) { + case SP_FS_IT_LEAVE: { + leaves++; + sp_expect_eq(t, s.count.closes, leaves); + break; + } + case SP_FS_IT_ENTRY: { + const enter_t* enter = find_enter(it, walk.entry.path); + if (enter) sp_expect_err_eq(t, sp_fs_it_enter(&walk), enter->err); + break; + } + } + } + sp_expect_ok(t, walk.err); + sp_fs_it_deinit(&walk); + + sim_end(&s); + + u32 expected = 0; + sp_carr_for(it->expect.yields, e) { + if (!it->expect.yields[e].path) break; + expected++; + } + sp_expect_eq(t, produced, expected); + sp_expect_eq(t, s.count.opens, it->expect.opens); + sp_expect_eq(t, s.count.nofollow, s.count.opens - 1); + sp_expect_eq(t, s.count.closes, s.count.dirs); + return SP_OK; +} diff --git a/test/fs/it/sim.c b/test/fs/it/sim.c index 649d9fe3..5eab92d9 100644 --- a/test/fs/it/sim.c +++ b/test/fs/it/sim.c @@ -20,6 +20,7 @@ typedef struct { typedef struct { sp_err_t open; sp_err_t walk; + const c8* failed; entry_t entries [MAX_WALK]; u32 opens; } expect_t; @@ -27,6 +28,7 @@ typedef struct { typedef struct { const c8* name; const c8* root; + u32 flags; bool recursive; u32 stop_after; sim_dir_t dirs [SIM_MAX_DIRS]; @@ -84,6 +86,7 @@ static const test_t tests [] = { }, .expect = { .walk = SP_ERR_SYS_NOT_FOUND, + .failed = "T/B", .opens = 2, }, }, @@ -105,19 +108,27 @@ static const test_t tests [] = { .name = "deinit_closes_open_frames", .root = "T", .recursive = true, - .stop_after = 2, + .stop_after = 1, .dirs = { { .path = "T", .entries = { { "B", SP_FS_KIND_DIR } } }, - { .path = "T/B", .entries = { { "C", SP_FS_KIND_FILE }, { "D", SP_FS_KIND_FILE } } }, + { .path = "T/B" }, }, .expect = { - .entries = { - { "T/B", "B", SP_FS_KIND_DIR }, - { "T/B/C", "C", SP_FS_KIND_FILE }, - }, + .entries = { { "T/B", "B", SP_FS_KIND_DIR } }, .opens = 2, }, }, + { + .name = "root_flags_forwarded", + .root = "T", + .flags = SP_SYS_OPEN_DIR_NO_FOLLOW, + .dirs = { + { .path = "T" }, + }, + .expect = { + .opens = 1, + }, + }, { .name = "path_fits_max", .root = "T", @@ -181,7 +192,7 @@ sp_test_each(fs, it_sim, test_t, tests, .serial = true) { sim_t s = sp_zero; sim_begin(&s, it->dirs); - sp_fs_it_t walk = sp_fs_it_new(mem, root); + sp_fs_it_t walk = sp_fs_it_new_at(mem, (sp_path_t) { .dir = sp_sys_get_root(0), .sub = root }, it->flags); sp_expect_err_eq(t, walk.err, it->expect.open); sp_expect(t, !walk.entry.path.data); @@ -200,6 +211,7 @@ sp_test_each(fs, it_sim, test_t, tests, .serial = true) { if (produced == it->stop_after) break; } sp_expect_err_eq(t, walk.err, it->expect.walk); + if (it->expect.failed) sp_expect_str_eq_c(t, walk.entry.path, it->expect.failed); sp_fs_it_deinit(&walk); sim_end(&s); @@ -212,5 +224,7 @@ sp_test_each(fs, it_sim, test_t, tests, .serial = true) { sp_expect_eq(t, produced, expected); sp_expect_eq(t, s.count.opens, it->expect.opens); sp_expect_eq(t, s.count.closes, s.count.dirs); + u32 root_nofollow = (it->flags & SP_SYS_OPEN_DIR_NO_FOLLOW) ? 1 : 0; + if (s.count.opens) sp_expect_eq(t, s.count.nofollow, s.count.opens - 1 + root_nofollow); return SP_OK; } From 44466c369cd45d3442d301df81d9102a10554292 Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Wed, 23 Sep 2026 20:09:36 -0400 Subject: [PATCH 22/72] fs: walker enter/leave; copy_tree and remove_dir on it --- sp.h | 288 ++++++++++++++++++++++++++--------------------------------- 1 file changed, 126 insertions(+), 162 deletions(-) diff --git a/sp.h b/sp.h index a315cb5c..3134a727 100644 --- a/sp.h +++ b/sp.h @@ -20224,60 +20224,80 @@ SP_PRIVATE sp_err_t sp_fs_it_join(sp_fs_it_t* it, u32 prefix, sp_str_t name, u32 return SP_OK; } -SP_PRIVATE sp_err_t sp_fs_it_open(sp_fs_it_t* it, sp_path_t path, u32 flags) { - u32 len = 0; - sp_try(sp_fs_it_join(it, 0, sp_fs_trim_path(path.sub), &len)); +SP_PRIVATE sp_err_t sp_fs_it_push(sp_fs_it_t* it, sp_path_t path, u32 len, u32 flags) { sp_fs_it_frame_t frame = { .len = len }; - sp_try(sp_fs_dir_open(&frame.dir, path.dir, sp_str(it->path, len), flags, sp_mem_slice(frame.buf, SP_FS_IT_BUF_SIZE))); + sp_try(sp_fs_dir_open(&frame.dir, path.dir, path.sub, flags, sp_mem_slice(frame.buf, SP_FS_IT_BUF_SIZE))); sp_da_push(it->stack, frame); return SP_OK; } SP_PRIVATE sp_err_t sp_fs_it_step(sp_fs_it_t* it) { - while (!sp_da_empty(it->stack)) { - sp_fs_it_frame_t* top = sp_da_back(it->stack); - top->dir.buf.data = top->buf; - - sp_fs_dir_entry_t d = sp_zero; - sp_try(sp_fs_dir_next(&top->dir, &d)); - if (!d.name.data) { - sp_da_pop(it->stack); - sp_try(sp_fs_dir_close(&top->dir)); - continue; - } + sp_fs_it_frame_t* top = sp_da_back(it->stack); + top->dir.buf.data = top->buf; + sp_fs_dir_entry_t d = sp_zero; + sp_try(sp_fs_dir_next(&top->dir, &d)); + + if (d.name.data) { u32 len = 0; sp_try(sp_fs_it_join(it, top->len, d.name, &len)); + it->yield = SP_FS_IT_ENTRY; it->entry = (sp_fs_entry_t) { .path = sp_str(it->path, len), .name = sp_str(it->path + len - d.name.len, d.name.len), .kind = d.kind, }; + it->at = (sp_path_t) { .dir = top->dir.dir.fd, .sub = it->entry.name }; // If the OS didn't give us a hint, specifically get it from metadata if (it->entry.kind == SP_FS_KIND_NONE) { - it->entry.kind = sp_fs_get_link_kind_at((sp_path_t) { .dir = top->dir.dir.fd, .sub = it->entry.name }); + it->entry.kind = sp_fs_get_link_kind_at(it->at); } - return SP_OK; } + + sp_str_t path = sp_str(it->path, top->len); + sp_da_pop(it->stack); + sp_try(sp_fs_dir_close(&top->dir)); + if (sp_da_empty(it->stack)) return SP_OK; + + sp_fs_it_frame_t* parent = sp_da_back(it->stack); + it->yield = SP_FS_IT_LEAVE; + it->entry = (sp_fs_entry_t) { + .path = path, + .name = sp_fs_get_name(path), + .kind = SP_FS_KIND_DIR, + }; + it->at = (sp_path_t) { .dir = parent->dir.dir.fd, .sub = it->entry.name }; return SP_OK; } bool sp_fs_it_next(sp_fs_it_t* it) { - if (it->err) return false; + if (it->err || sp_da_empty(it->stack)) return false; it->entry = sp_zero_s(sp_fs_entry_t); + it->at = sp_zero_s(sp_path_t); it->err = sp_fs_it_step(it); if (it->err) sp_fs_it_unwind(it); return !it->err && !sp_da_empty(it->stack); } -bool sp_fs_it_walk(sp_fs_it_t* it) { - return false; +sp_err_t sp_fs_it_enter(sp_fs_it_t* it) { + sp_assert(it->yield == SP_FS_IT_ENTRY && !sp_str_empty(it->entry.path)); + sp_assert(sp_da_back(it->stack)->len != it->entry.path.len); + return sp_fs_it_push(it, it->at, it->entry.path.len, SP_SYS_OPEN_DIR_NO_FOLLOW); } -sp_err_t sp_fs_it_enter(sp_fs_it_t* it) { - return SP_ERR_SYS_UNSUPPORTED; +bool sp_fs_it_walk(sp_fs_it_t* it) { + while (sp_fs_it_next(it)) { + if (it->yield == SP_FS_IT_LEAVE) continue; + if (it->entry.kind != SP_FS_KIND_DIR) return true; + + it->err = sp_fs_it_enter(it); + if (!it->err) return true; + sp_fs_it_unwind(it); + return false; + } + return false; } void sp_fs_it_deinit(sp_fs_it_t* it) { @@ -20285,6 +20305,20 @@ void sp_fs_it_deinit(sp_fs_it_t* it) { sp_da_free(it->stack); } +sp_fs_it_t sp_fs_it_new_at(sp_mem_t mem, sp_path_t path, u32 flags) { + sp_fs_it_t it = sp_zero; + sp_da_init(mem, it.stack); + + u32 len = 0; + it.err = sp_fs_it_join(&it, 0, sp_fs_trim_path(path.sub), &len); + if (!it.err) it.err = sp_fs_it_push(&it, (sp_path_t) { .dir = path.dir, .sub = sp_str(it.path, len) }, len, flags); + return it; +} + +sp_fs_it_t sp_fs_it_new(sp_mem_t mem, sp_str_t path) { + return sp_fs_it_new_at(mem, sp_path_at_root(path), 0); +} + SP_PRIVATE sp_fs_entry_t sp_fs_entry_copy(sp_mem_t mem, sp_fs_entry_t entry) { sp_str_t path = sp_str_copy(mem, entry.path); return (sp_fs_entry_t) { @@ -20314,27 +20348,6 @@ sp_err_t sp_fs_collect_recursive(sp_mem_t mem, sp_str_t path, sp_da(sp_fs_entry_ return it.err; } -typedef struct { - sp_fs_dir_t dir; - SP_ALIGNED u8 buf [SP_FS_IT_BUF_SIZE]; - u32 name_offset; - u32 name_len; -} sp_fs_remove_frame_t; - -SP_PRIVATE sp_err_t sp_fs_remove_frame_push(sp_da(sp_fs_remove_frame_t)* stack, sp_path_t path, u32 name_offset) { - sp_fs_remove_frame_t frame = { .name_offset = name_offset, .name_len = path.sub.len }; - sp_try(sp_fs_dir_open(&frame.dir, path.dir, path.sub, SP_SYS_OPEN_DIR_NO_FOLLOW, sp_mem_slice(frame.buf, SP_FS_IT_BUF_SIZE))); - sp_da_push(*stack, frame); - return SP_OK; -} - -SP_PRIVATE sp_path_t sp_fs_remove_frame_path(sp_fs_remove_frame_t* parent, sp_fs_remove_frame_t* frame) { - return (sp_path_t) { - .dir = parent->dir.dir.fd, - .sub = sp_str(sp_ptr_cast(const c8*, parent->buf) + frame->name_offset, frame->name_len), - }; -} - sp_err_t sp_fs_remove_dir(sp_str_t path) { return sp_fs_remove_dir_at(sp_path_at_root(path)); } @@ -20346,8 +20359,9 @@ sp_err_t sp_fs_remove_dir(sp_str_t path) { into subdirectories, and unlink them. Fails if this root (i.e. fd + path) does not refer to a directory, and unlinks the root at the end. - This function uses O(depth) memory. It can be used with directories of - arbitrary depth, and each directory is opened exactly once. + This function uses O(depth) memory, and each directory is opened exactly + once. Every path under the root must fit in SP_PATH_MAX; a deeper tree fails + with NAME_TOO_LONG, and can be removed by opening a handle partway down. # SYMLINKS @@ -20409,47 +20423,39 @@ sp_err_t sp_fs_remove_dir_at(sp_path_t path) { sp_path_t root = { .dir = path.dir, .sub = sub }; sp_mem_arena_marker_t s = sp_mem_begin_scratch(); - sp_da(sp_fs_remove_frame_t) stack = sp_da_new(s.mem, sp_fs_remove_frame_t); - sp_err_t err = sp_fs_remove_frame_push(&stack, root, 0); - - while (!err && !sp_da_empty(stack)) { - u64 depth = sp_da_size(stack) - 1; - sp_fs_remove_frame_t* top = &stack[depth]; - top->dir.buf.data = top->buf; - - sp_fs_dir_entry_t entry = sp_zero; - err = sp_fs_dir_next(&top->dir, &entry); - if (err) break; - - if (!entry.name.data) { - sp_path_t self = depth ? sp_fs_remove_frame_path(&stack[depth - 1], top) : root; - sp_fs_dir_close(&top->dir); - err = sp_sys_rmdir_s(self.dir, self.sub); - sp_da_pop(stack); - } - else { - sp_path_t child = { .dir = top->dir.dir.fd, .sub = entry.name }; - u32 offset = (u32)(sp_ptr_cast(const u8*, entry.name.data) - top->buf); - - // entry kinds are hints; flip on a contradicting error so a lying or - // absent d_type can't strand an entry - if (entry.kind == SP_FS_KIND_DIR) { - err = sp_fs_remove_frame_push(&stack, child, offset); - if (err == SP_ERR_SYS_NOT_DIR || err == SP_ERR_SYS_LOOP) err = sp_sys_unlink_s(child.dir, child.sub); + sp_fs_it_t it = sp_fs_it_new_at(s.mem, root, SP_SYS_OPEN_DIR_NO_FOLLOW); + sp_err_t err = it.err; + + while (!err && sp_fs_it_next(&it)) { + switch (it.yield) { + case SP_FS_IT_LEAVE: { + err = sp_sys_rmdir_s(it.at.dir, it.at.sub); + break; } - else { - err = sp_sys_unlink_s(child.dir, child.sub); - if (err == SP_ERR_SYS_IS_DIR) err = sp_fs_remove_frame_push(&stack, child, offset); + case SP_FS_IT_ENTRY: { + // entry kinds are hints; flip on a contradicting error so a lying or + // absent d_type can't strand an entry + if (it.entry.kind == SP_FS_KIND_DIR) { + err = sp_fs_it_enter(&it); + if (err == SP_ERR_SYS_NOT_DIR || err == SP_ERR_SYS_LOOP) err = sp_sys_unlink_s(it.at.dir, it.at.sub); + } + else { + err = sp_sys_unlink_s(it.at.dir, it.at.sub); + if (err == SP_ERR_SYS_IS_DIR) err = sp_fs_it_enter(&it); + } + break; } } if (err == SP_ERR_SYS_NOT_FOUND) err = SP_OK; } + if (!err) err = it.err; - sp_da_for(stack, it) { - sp_fs_dir_close(&stack[it].dir); - } + sp_fs_it_deinit(&it); sp_mem_end_scratch(s); - return err; + if (err) return err; + + err = sp_sys_rmdir_s(root.dir, root.sub); + return err == SP_ERR_SYS_NOT_FOUND ? SP_OK : err; } static sp_atomic_s32_t sp_fs_atomic_sequence; @@ -20654,93 +20660,62 @@ sp_err_t sp_fs_copy_file_at(sp_path_t from, sp_path_t to, sp_fs_atomic_mode_t mo return sp_fs_atomic_commit(&af, mode); } -typedef struct { - sp_fs_dir_t from; - sp_sys_fd_t to; - SP_ALIGNED u8 buf [SP_FS_IT_BUF_SIZE]; -} sp_fs_copy_frame_t; - -SP_PRIVATE sp_err_t sp_fs_copy_frame_open(sp_fs_copy_frame_t* frame, sp_path_t from, sp_path_t to) { - sp_try(sp_fs_dir_open(&frame->from, from.dir, from.sub, 0, sp_mem_slice(frame->buf, SP_FS_IT_BUF_SIZE))); - - sp_err_t err = sp_fs_create_dir_at(to); - if (!err) err = sp_sys_open_dir_s(to.dir, to.sub, 0, &frame->to); - if (err) sp_fs_dir_close(&frame->from); - return err; -} - -SP_PRIVATE void sp_fs_copy_frame_close(sp_fs_copy_frame_t* frame) { - sp_fs_dir_close(&frame->from); - sp_sys_close(frame->to); -} - SP_PRIVATE bool sp_fs_is_same_file(const sp_sys_file_meta_t* a, const sp_sys_file_meta_t* b) { return a->device == b->device && a->id == b->id; } sp_err_t sp_fs_copy_tree_at(sp_path_t from, sp_path_t to, sp_fs_atomic_mode_t mode) { - sp_fs_copy_frame_t frame = sp_zero; - sp_try(sp_fs_copy_frame_open(&frame, from, to)); - sp_sys_file_meta_t source = sp_zero; - sp_sys_file_meta_t root = sp_zero; - sp_err_t err = sp_sys_get_file_metadata(frame.from.dir.fd, &source); - if (!err) err = sp_sys_get_file_metadata(frame.to, &root); - if (!err && sp_fs_is_same_file(&source, &root)) err = SP_ERR_SYS_INVALID; - if (err) { - sp_fs_copy_frame_close(&frame); - return err; - } + sp_try(sp_sys_get_path_metadata_s(from.dir, from.sub, &source)); sp_mem_arena_marker_t s = sp_mem_begin_scratch(); - sp_da(sp_fs_copy_frame_t) stack = sp_da_new(s.mem, sp_fs_copy_frame_t); - sp_da_push(stack, frame); - - while (!sp_da_empty(stack)) { - sp_fs_copy_frame_t* top = sp_da_back(stack); - top->from.buf.data = top->buf; - - sp_fs_dir_entry_t entry = sp_zero; - err = sp_fs_dir_next(&top->from, &entry); - if (err) break; - - if (!entry.name.data) { - sp_fs_copy_frame_close(top); - sp_da_pop(stack); - continue; - } - - sp_path_t src = { .dir = top->from.dir.fd, .sub = entry.name }; - sp_path_t dst = { .dir = top->to, .sub = entry.name }; - sp_sys_file_meta_t meta = sp_zero; - err = sp_sys_get_link_metadata_s(src.dir, src.sub, &meta); - if (err) break; - - switch (meta.kind) { - case SP_FS_KIND_FILE: { - err = sp_fs_copy_file_at(src, dst, mode); + sp_fs_it_t it = sp_fs_it_new_at(s.mem, from, 0); + sp_da(sp_sys_fd_t) dest = sp_da_new(s.mem, sp_sys_fd_t); + + sp_sys_fd_t root = SP_SYS_INVALID_FD; + sp_sys_file_meta_t target = sp_zero; + sp_err_t err = it.err; + if (!err) err = sp_fs_create_dir_at(to); + if (!err) err = sp_sys_open_dir_s(to.dir, to.sub, 0, &root); + if (!err) sp_da_push(dest, root); + if (!err) err = sp_sys_get_file_metadata(root, &target); + if (!err && sp_fs_is_same_file(&source, &target)) err = SP_ERR_SYS_INVALID; + + while (!err && sp_fs_it_next(&it)) { + sp_path_t dst = { .dir = *sp_da_back(dest), .sub = it.entry.name }; + switch (it.yield) { + case SP_FS_IT_LEAVE: { + sp_sys_close(dst.dir); + sp_da_pop(dest); break; } - case SP_FS_KIND_SYMLINK: { - err = sp_fs_copy_link_at(src, dst, mode); - break; - } - case SP_FS_KIND_DIR: { - if (sp_fs_is_same_file(&meta, &root)) break; - err = sp_fs_copy_frame_open(&frame, src, dst); - if (!err) sp_da_push(stack, frame); - break; - } - case SP_FS_KIND_NONE: { - err = SP_ERR_SYS_UNSUPPORTED; + case SP_FS_IT_ENTRY: { + switch (it.entry.kind) { + case SP_FS_KIND_FILE: err = sp_fs_copy_file_at(it.at, dst, mode); break; + case SP_FS_KIND_SYMLINK: err = sp_fs_copy_link_at(it.at, dst, mode); break; + case SP_FS_KIND_NONE: err = SP_ERR_SYS_UNSUPPORTED; break; + case SP_FS_KIND_DIR: { + sp_sys_file_meta_t meta = sp_zero; + err = sp_sys_get_link_metadata_s(it.at.dir, it.at.sub, &meta); + if (err || sp_fs_is_same_file(&meta, &target)) break; + + sp_sys_fd_t fd = SP_SYS_INVALID_FD; + err = sp_fs_it_enter(&it); + if (!err) err = sp_fs_create_dir_at(dst); + if (!err) err = sp_sys_open_dir_s(dst.dir, dst.sub, 0, &fd); + if (!err) sp_da_push(dest, fd); + break; + } + } break; } } - if (err) break; } + if (!err) err = it.err; - sp_da_for(stack, it) { - sp_fs_copy_frame_close(&stack[it]); + sp_fs_it_deinit(&it); + sp_da_for(dest, i) { + sp_sys_close(dest[i]); } sp_mem_end_scratch(s); return err; @@ -20759,17 +20734,6 @@ sp_err_t sp_fs_copy_at(sp_path_t from, sp_path_t to, sp_fs_atomic_mode_t mode) { SP_UNREACHABLE_RETURN(SP_OK); } -sp_fs_it_t sp_fs_it_new_at(sp_mem_t mem, sp_path_t path, u32 flags) { - sp_fs_it_t it = sp_zero; - sp_da_init(mem, it.stack); - it.err = sp_fs_it_open(&it, path, flags); - return it; -} - -sp_fs_it_t sp_fs_it_new(sp_mem_t mem, sp_str_t path) { - return sp_fs_it_new_at(mem, sp_path_at_root(path), 0); -} - // // time // From 88b791680620ac341166286060a74f0d47e7f80a Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Wed, 23 Sep 2026 20:13:02 -0400 Subject: [PATCH 23/72] sys: open_dir NO_FOLLOW reports LOOP for symlinks on every platform --- sp.h | 14 +++++++++++--- test/sys/harness.h | 2 +- test/sys/open.c | 30 ++++++++++++++++++++++++++++++ test/sys/sys.c | 2 +- 4 files changed, 43 insertions(+), 5 deletions(-) diff --git a/sp.h b/sp.h index 3134a727..18810b35 100644 --- a/sp.h +++ b/sp.h @@ -8365,6 +8365,14 @@ sp_err_t sp_sys_open_p(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_open_mode #endif } +SP_PRIVATE sp_err_t sp_sys_open_dir_nofollow_err(sp_sys_fd_t fd, const c8* path, u32 len, u32 flags, sp_err_t err) { + if (err != SP_ERR_SYS_NOT_DIR || !(flags & SP_SYS_OPEN_DIR_NO_FOLLOW)) return err; + + sp_sys_file_meta_t st = sp_zero; + if (sp_sys_get_link_metadata_p(fd, path, len, &st)) return err; + return st.kind == SP_FS_KIND_SYMLINK ? SP_ERR_SYS_LOOP : err; +} + sp_err_t sp_sys_open_dir_p(sp_sys_fd_t fd, const c8* path, u32 len, u32 flags, sp_sys_fd_t* out) { *out = SP_SYS_INVALID_FD; if (flags & ~(u32)SP_SYS_OPEN_DIR_NO_FOLLOW) return SP_ERR_SYS_INVALID; @@ -8377,7 +8385,7 @@ sp_err_t sp_sys_open_dir_p(sp_sys_fd_t fd, const c8* path, u32 len, u32 flags, s sp_sys_fd_t handle = SP_SYS_INVALID_FD; sp_nt_status_t status = sp_sys_nt_open(fd, sp_str(path, len), access, share, SP_NT_FILE_OPEN, options, FILE_ATTRIBUTE_NORMAL, &handle); - if (!SP_NT_SUCCESS(status)) return sp_sys_err_from_nt(status); + if (!SP_NT_SUCCESS(status)) return sp_sys_open_dir_nofollow_err(fd, path, len, flags, sp_sys_err_from_nt(status)); if (flags & SP_SYS_OPEN_DIR_NO_FOLLOW) { BY_HANDLE_FILE_INFORMATION info = sp_zero; @@ -8401,7 +8409,7 @@ sp_err_t sp_sys_open_dir_p(sp_sys_fd_t fd, const c8* path, u32 len, u32 flags, s u32 o = SP_SYS_LINUX_O_RDONLY | SP_SYS_LINUX_O_DIRECTORY | SP_SYS_LINUX_O_CLOEXEC; if (flags & SP_SYS_OPEN_DIR_NO_FOLLOW) o |= SP_SYS_LINUX_O_NOFOLLOW; s64 rc = sp_syscall(SP_SYSCALL_NUM_OPENAT, fd, buf, o, 0); - if (rc < 0) return sp_sys_err_from_errno(-rc); + if (rc < 0) return sp_sys_open_dir_nofollow_err(fd, path, len, flags, sp_sys_err_from_errno(-rc)); *out = (sp_sys_fd_t)rc; return SP_OK; @@ -8412,7 +8420,7 @@ sp_err_t sp_sys_open_dir_p(sp_sys_fd_t fd, const c8* path, u32 len, u32 flags, s s32 o = O_RDONLY | O_DIRECTORY | O_CLOEXEC; if (flags & SP_SYS_OPEN_DIR_NO_FOLLOW) o |= O_NOFOLLOW; s32 rc = openat((int)fd, buf, o, 0); - if (rc < 0) return sp_sys_err_from_errno(errno); + if (rc < 0) return sp_sys_open_dir_nofollow_err(fd, path, len, flags, sp_sys_err_from_errno(errno)); *out = (sp_sys_fd_t)rc; return SP_OK; diff --git a/test/sys/harness.h b/test/sys/harness.h index ec61982f..fa1bfdbf 100644 --- a/test/sys/harness.h +++ b/test/sys/harness.h @@ -39,7 +39,7 @@ typedef struct { sys_step_kind_t kind; union { struct { u32 slot; const c8* path; sp_sys_open_mode_t mode; u32 flags; sp_err_t err; } open; - struct { u32 slot; const c8* path; sp_err_t err; } open_dir; + struct { u32 slot; const c8* path; u32 flags; sp_err_t err; } open_dir; struct { u32 slot; u64 count; const c8* expect; sp_err_t err; } read; struct { u32 slot; const c8* data; sp_err_t err; } write; struct { u32 slot; const c8* data; u64 offset; sp_err_t err; } pwrite; diff --git a/test/sys/open.c b/test/sys/open.c index e6486db8..1a3fdd41 100644 --- a/test/sys/open.c +++ b/test/sys/open.c @@ -257,6 +257,36 @@ static const sys_case_t sys_open_dir_cases [] = { { .kind = SYS_STEP_OPEN_DIR, .open_dir = { .path = "dir", .err = SP_ERR_SYS_NOT_FOUND } }, }, }, + { + .name = "follows_dir_symlink", + .setup = { + { .path = "dir", .kind = SYS_SETUP_DIR }, + { .path = "link", .kind = SYS_SETUP_SYMLINK, .target = "dir" }, + }, + .steps = { + { .kind = SYS_STEP_OPEN_DIR, .open_dir = { .path = "link" } }, + }, + }, + { + .name = "nofollow_refuses_dir_symlink", + .setup = { + { .path = "dir", .kind = SYS_SETUP_DIR }, + { .path = "link", .kind = SYS_SETUP_SYMLINK, .target = "dir" }, + }, + .steps = { + { .kind = SYS_STEP_OPEN_DIR, .open_dir = { .path = "link", .flags = SP_SYS_OPEN_DIR_NO_FOLLOW, .err = SP_ERR_SYS_LOOP } }, + }, + }, + { + .name = "nofollow_refuses_file_symlink", + .setup = { + { .path = "file.bin", .content = "A" }, + { .path = "link", .kind = SYS_SETUP_SYMLINK, .target = "file.bin" }, + }, + .steps = { + { .kind = SYS_STEP_OPEN_DIR, .open_dir = { .path = "link", .flags = SP_SYS_OPEN_DIR_NO_FOLLOW, .err = SP_ERR_SYS_LOOP } }, + }, + }, }; sp_test_each_fn(sys, open, sys_case_t, sys_open_cases, sys_case_run); diff --git a/test/sys/sys.c b/test/sys/sys.c index f441ebc1..16990a91 100644 --- a/test/sys/sys.c +++ b/test/sys/sys.c @@ -104,7 +104,7 @@ sp_err_t sys_case_run(sp_test_t* t, sys_case_t* c) { case SYS_STEP_OPEN_DIR: { sp_sys_fd_t fd = SP_SYS_INVALID_FD; sp_str_t path = sp_cstr_as_str(step->open_dir.path); - sp_err_t err = sp_sys_open_dir_s(sandbox_fd, path, 0, &fd); + sp_err_t err = sp_sys_open_dir_s(sandbox_fd, path, step->open_dir.flags, &fd); sys_check_err(t, err, step->open_dir.err); if (!err) { if (step->open_dir.err) sp_sys_close(fd); From 3a0525125420b248a9569b9f4d57585267afa01a Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Wed, 23 Sep 2026 20:27:07 -0400 Subject: [PATCH 24/72] fs: walker implementation review --- sp.h | 71 ++++++++++++++++++++---------------------- test/fs/it/enter_sim.c | 23 ++++++++------ 2 files changed, 46 insertions(+), 48 deletions(-) diff --git a/sp.h b/sp.h index 18810b35..0768c896 100644 --- a/sp.h +++ b/sp.h @@ -3063,6 +3063,7 @@ typedef struct { sp_fs_dir_t dir; SP_ALIGNED u8 buf [SP_FS_IT_BUF_SIZE]; u32 len; + u32 name_len; } sp_fs_it_frame_t; typedef enum { @@ -20233,7 +20234,7 @@ SP_PRIVATE sp_err_t sp_fs_it_join(sp_fs_it_t* it, u32 prefix, sp_str_t name, u32 } SP_PRIVATE sp_err_t sp_fs_it_push(sp_fs_it_t* it, sp_path_t path, u32 len, u32 flags) { - sp_fs_it_frame_t frame = { .len = len }; + sp_fs_it_frame_t frame = { .len = len, .name_len = path.sub.len }; sp_try(sp_fs_dir_open(&frame.dir, path.dir, path.sub, flags, sp_mem_slice(frame.buf, SP_FS_IT_BUF_SIZE))); sp_da_push(it->stack, frame); return SP_OK; @@ -20264,7 +20265,6 @@ SP_PRIVATE sp_err_t sp_fs_it_step(sp_fs_it_t* it) { return SP_OK; } - sp_str_t path = sp_str(it->path, top->len); sp_da_pop(it->stack); sp_try(sp_fs_dir_close(&top->dir)); if (sp_da_empty(it->stack)) return SP_OK; @@ -20272,8 +20272,8 @@ SP_PRIVATE sp_err_t sp_fs_it_step(sp_fs_it_t* it) { sp_fs_it_frame_t* parent = sp_da_back(it->stack); it->yield = SP_FS_IT_LEAVE; it->entry = (sp_fs_entry_t) { - .path = path, - .name = sp_fs_get_name(path), + .path = sp_str(it->path, top->len), + .name = sp_str(it->path + top->len - top->name_len, top->name_len), .kind = SP_FS_KIND_DIR, }; it->at = (sp_path_t) { .dir = parent->dir.dir.fd, .sub = it->entry.name }; @@ -20291,7 +20291,7 @@ bool sp_fs_it_next(sp_fs_it_t* it) { sp_err_t sp_fs_it_enter(sp_fs_it_t* it) { sp_assert(it->yield == SP_FS_IT_ENTRY && !sp_str_empty(it->entry.path)); - sp_assert(sp_da_back(it->stack)->len != it->entry.path.len); + sp_assert(sp_da_back(it->stack)->len < it->entry.path.len); return sp_fs_it_push(it, it->at, it->entry.path.len, SP_SYS_OPEN_DIR_NO_FOLLOW); } @@ -20301,9 +20301,8 @@ bool sp_fs_it_walk(sp_fs_it_t* it) { if (it->entry.kind != SP_FS_KIND_DIR) return true; it->err = sp_fs_it_enter(it); - if (!it->err) return true; - sp_fs_it_unwind(it); - return false; + if (it->err) sp_fs_it_unwind(it); + return !it->err; } return false; } @@ -20317,9 +20316,10 @@ sp_fs_it_t sp_fs_it_new_at(sp_mem_t mem, sp_path_t path, u32 flags) { sp_fs_it_t it = sp_zero; sp_da_init(mem, it.stack); + path.sub = sp_fs_trim_path(path.sub); u32 len = 0; - it.err = sp_fs_it_join(&it, 0, sp_fs_trim_path(path.sub), &len); - if (!it.err) it.err = sp_fs_it_push(&it, (sp_path_t) { .dir = path.dir, .sub = sp_str(it.path, len) }, len, flags); + it.err = sp_fs_it_join(&it, 0, path.sub, &len); + if (!it.err) it.err = sp_fs_it_push(&it, path, len, flags); return it; } @@ -20680,41 +20680,36 @@ sp_err_t sp_fs_copy_tree_at(sp_path_t from, sp_path_t to, sp_fs_atomic_mode_t mo sp_fs_it_t it = sp_fs_it_new_at(s.mem, from, 0); sp_da(sp_sys_fd_t) dest = sp_da_new(s.mem, sp_sys_fd_t); - sp_sys_fd_t root = SP_SYS_INVALID_FD; + sp_sys_fd_t fd = SP_SYS_INVALID_FD; sp_sys_file_meta_t target = sp_zero; sp_err_t err = it.err; if (!err) err = sp_fs_create_dir_at(to); - if (!err) err = sp_sys_open_dir_s(to.dir, to.sub, 0, &root); - if (!err) sp_da_push(dest, root); - if (!err) err = sp_sys_get_file_metadata(root, &target); + if (!err) err = sp_sys_open_dir_s(to.dir, to.sub, 0, &fd); + if (!err) sp_da_push(dest, fd); + if (!err) err = sp_sys_get_file_metadata(fd, &target); if (!err && sp_fs_is_same_file(&source, &target)) err = SP_ERR_SYS_INVALID; while (!err && sp_fs_it_next(&it)) { + if (it.yield == SP_FS_IT_LEAVE) { + sp_sys_close(*sp_da_back(dest)); + sp_da_pop(dest); + continue; + } + sp_path_t dst = { .dir = *sp_da_back(dest), .sub = it.entry.name }; - switch (it.yield) { - case SP_FS_IT_LEAVE: { - sp_sys_close(dst.dir); - sp_da_pop(dest); - break; - } - case SP_FS_IT_ENTRY: { - switch (it.entry.kind) { - case SP_FS_KIND_FILE: err = sp_fs_copy_file_at(it.at, dst, mode); break; - case SP_FS_KIND_SYMLINK: err = sp_fs_copy_link_at(it.at, dst, mode); break; - case SP_FS_KIND_NONE: err = SP_ERR_SYS_UNSUPPORTED; break; - case SP_FS_KIND_DIR: { - sp_sys_file_meta_t meta = sp_zero; - err = sp_sys_get_link_metadata_s(it.at.dir, it.at.sub, &meta); - if (err || sp_fs_is_same_file(&meta, &target)) break; - - sp_sys_fd_t fd = SP_SYS_INVALID_FD; - err = sp_fs_it_enter(&it); - if (!err) err = sp_fs_create_dir_at(dst); - if (!err) err = sp_sys_open_dir_s(dst.dir, dst.sub, 0, &fd); - if (!err) sp_da_push(dest, fd); - break; - } - } + switch (it.entry.kind) { + case SP_FS_KIND_FILE: err = sp_fs_copy_file_at(it.at, dst, mode); break; + case SP_FS_KIND_SYMLINK: err = sp_fs_copy_link_at(it.at, dst, mode); break; + case SP_FS_KIND_NONE: err = SP_ERR_SYS_UNSUPPORTED; break; + case SP_FS_KIND_DIR: { + sp_sys_file_meta_t meta = sp_zero; + err = sp_sys_get_link_metadata_s(it.at.dir, it.at.sub, &meta); + if (err || sp_fs_is_same_file(&meta, &target)) break; + + err = sp_fs_it_enter(&it); + if (!err) err = sp_fs_create_dir_at(dst); + if (!err) err = sp_sys_open_dir_s(dst.dir, dst.sub, 0, &fd); + if (!err) sp_da_push(dest, fd); break; } } diff --git a/test/fs/it/enter_sim.c b/test/fs/it/enter_sim.c index f480e7e1..3a0ec299 100644 --- a/test/fs/it/enter_sim.c +++ b/test/fs/it/enter_sim.c @@ -11,6 +11,7 @@ typedef struct { typedef struct { const c8* path; + const c8* name; sp_fs_kind_t kind; sp_fs_it_yield_t yield; } yield_t; @@ -37,10 +38,10 @@ static const test_t tests [] = { .enter = { { "T/B" } }, .expect = { .yields = { - { "T/B", SP_FS_KIND_DIR }, - { "T/B/D", SP_FS_KIND_FILE }, - { "T/B", SP_FS_KIND_DIR, SP_FS_IT_LEAVE }, - { "T/C", SP_FS_KIND_FILE }, + { "T/B", "B", SP_FS_KIND_DIR }, + { "T/B/D", "D", SP_FS_KIND_FILE }, + { "T/B", "B", SP_FS_KIND_DIR, SP_FS_IT_LEAVE }, + { "T/C", "C", SP_FS_KIND_FILE }, }, .opens = 2, }, @@ -53,7 +54,7 @@ static const test_t tests [] = { }, .expect = { .yields = { - { "T/B", SP_FS_KIND_DIR }, + { "T/B", "B", SP_FS_KIND_DIR }, }, .opens = 1, }, @@ -67,9 +68,9 @@ static const test_t tests [] = { .enter = { { "T/B" } }, .expect = { .yields = { - { "T/B", SP_FS_KIND_FILE }, - { "T/B/C", SP_FS_KIND_FILE }, - { "T/B", SP_FS_KIND_DIR, SP_FS_IT_LEAVE }, + { "T/B", "B", SP_FS_KIND_FILE }, + { "T/B/C", "C", SP_FS_KIND_FILE }, + { "T/B", "B", SP_FS_KIND_DIR, SP_FS_IT_LEAVE }, }, .opens = 2, }, @@ -83,8 +84,8 @@ static const test_t tests [] = { .enter = { { "T/B", SP_ERR_SYS_IO } }, .expect = { .yields = { - { "T/B", SP_FS_KIND_DIR }, - { "T/C", SP_FS_KIND_FILE }, + { "T/B", "B", SP_FS_KIND_DIR }, + { "T/C", "C", SP_FS_KIND_FILE }, }, .opens = 2, }, @@ -113,6 +114,8 @@ sp_test_each(fs, it_enter_sim, test_t, tests, .serial = true) { if (produced < MAX_YIELDS && it->expect.yields[produced].path) { const yield_t* want = &it->expect.yields[produced]; sp_expect_str_eq_c(t, walk.entry.path, want->path); + sp_expect_str_eq_c(t, walk.entry.name, want->name); + sp_expect_str_eq(t, walk.at.sub, walk.entry.name); sp_expect_eq(t, (u32)walk.entry.kind, (u32)want->kind); sp_expect_eq(t, (u32)walk.yield, (u32)want->yield); } From 98db7de47244832816d78b5a46dce526d2d940b9 Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Thu, 24 Sep 2026 14:16:04 -0400 Subject: [PATCH 25/72] cleanup, fs example --- Makefile | 2 +- README.md | 2 +- example/filesystem.c | 118 +++++++++++++++++++++++++++++ example/ls.c | 2 +- example/wc.c | 2 +- example/zero_copy.c | 2 +- sp.h | 164 ++++++++++++++++++++--------------------- sp/sp_test.h | 4 +- spn.toml | 4 + test/fs/canonicalize.c | 2 +- test/fs/collect.c | 1 + test/fs/copy.c | 31 -------- test/fs/create_dir.c | 2 +- test/fs/get_cwd.c | 6 +- test/fs/it/enter.c | 1 + test/fs/it/enter_sim.c | 1 + test/fs/it/os.c | 1 + test/fs/it/sim.c | 1 + test/tools/test.h | 2 +- tools/sp.c | 2 +- 20 files changed, 223 insertions(+), 127 deletions(-) create mode 100644 example/filesystem.c diff --git a/Makefile b/Makefile index 1637e9e2..e170956e 100644 --- a/Makefile +++ b/Makefile @@ -109,7 +109,7 @@ endif TESTS = amalg app array asset cli etc env format fmon glob ht io math process ps rb str sys thread time mem prompt leak qsort term BENCHES = glob heap -EXAMPLES = app array cargo cli format hash_table io zero_copy ls palette post prompt prompt_fancy serve signal tls wc +EXAMPLES = app array cargo cli format hash_table io zero_copy ls palette post prompt prompt_fancy serve signal tls wc filesystem TRIPLES = \ x86_64-linux-none x86_64-linux-gnu x86_64-linux-musl \ aarch64-linux-none aarch64-linux-gnu aarch64-linux-musl \ diff --git a/README.md b/README.md index 831c4a2e..10888af5 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ s32 compare_entries(const void* pa, const void* pb) { s32 main(s32 num_args, const c8** args) { sp_mem_t mem = sp_mem_os_new(); - sp_str_t cwd = sp_fs_get_cwd(mem); + sp_str_t cwd = sp_fs_get_cwd_path(mem); sp_str_t dir = cwd; if (num_args == 2) dir = sp_fs_join_path(mem, cwd, sp_cstr_as_str(args[1])); diff --git a/example/filesystem.c b/example/filesystem.c new file mode 100644 index 00000000..0fc1239b --- /dev/null +++ b/example/filesystem.c @@ -0,0 +1,118 @@ +#define SP_IMPLEMENTATION +#include "sp.h" +#include "sp/sp_cli.h" + +typedef struct { + sp_str_t dir; + bool walk; +} args_t; + +typedef struct { + args_t args; + sp_mem_heap_t* heap; + sp_mem_t mem; +} ctx_t; + +void print_entry(sp_fs_entry_t entry, u32 depth) { + sp_print("{:$}", sp_fmt_uint(depth * 2), sp_fmt_cstr("")); + sp_print("{.gray}", sp_fmt_cstr("│ ")); + switch (entry.kind) { + case SP_FS_KIND_DIR: sp_log("{.blue .bold}", sp_fmt_str(entry.name)); break; + case SP_FS_KIND_FILE: sp_log("{}", sp_fmt_str(entry.name)); break; + case SP_FS_KIND_SYMLINK: sp_log("{.cyan} -> {.gray}", sp_fmt_str(entry.name), sp_fmt_str(entry.path)); break; + case SP_FS_KIND_NONE: break; + } + +} + +void iterate(sp_path_t path) { + sp_mem_arena_marker_t s = sp_mem_begin_scratch(); + + print_entry((sp_fs_entry_t) { + .kind = SP_FS_KIND_DIR, + .name = sp_fs_get_stem(path.sub) + }, 0); + + sp_fs_it_t it = sp_fs_it_new_at(s.mem, path, 0); + while (sp_fs_it_next(&it)) { + if (it.yield == SP_FS_IT_LEAVE) { + continue; + } + print_entry(it.entry, sp_da_size(it.stack)); + + // switch (it.entry.kind) { + // case SP_FS_KIND_DIR: sp_fs_it_enter(&it); + // case SP_FS_KIND_FILE: + // case SP_FS_KIND_SYMLINK: + // case SP_FS_KIND_NONE: break; + // } + } + sp_mem_end_scratch(s); +} + +void walk(sp_path_t path) { + sp_mem_arena_marker_t s = sp_mem_begin_scratch(); + sp_fs_it_t it = sp_fs_it_new_at(s.mem, path, 0); + while (sp_fs_it_walk(&it)) { + print_entry(it.entry, sp_da_size(it.stack) - 1); + } + sp_mem_end_scratch(s); +} + +sp_cli_result_t command(sp_cli_t* cli) { + ctx_t* ctx = sp_ptr_cast(ctx_t*, cli->user_data); + args_t args = ctx->args; + + sp_path_t path = { + .dir = sp_sys_get_root(0), + .sub = sp_str_empty(args.dir) ? + sp_str_lit(".") : + args.dir + }; + sp_log("{}", sp_fmt_int(path.dir)); + + if (args.walk) { + walk(path); + } else { + iterate(path); + } + return SP_CLI_OK; +} + +s32 run(s32 num_args, const c8** args) { + ctx_t ctx = sp_zero; + ctx.heap = sp_mem_heap_new(); + ctx.mem = sp_mem_heap_as_allocator(ctx.heap); + + sp_cli_cmd_t root = { + .name = "filesystem", + .summary = "", + .opts = { + { + .brief = 'w', + .name = "walk", + .kind = SP_CLI_OPT_BOOLEAN, + .summary = "Walk (as opposed to flat traversal)", + .ptr = &ctx.args.walk + }, + }, + .args = { + { + .name = "dir", + .summary = "The directory", + .arity = SP_CLI_ARG_OPTIONAL, + .kind = SP_CLI_OPT_STR, + .ptr = &ctx.args.dir + } + }, + .handler = command, + }; + + return sp_cli_main((sp_cli_desc_t) { + .root = &root, + .args = args, + .num_args = num_args, + .user_data = &ctx + }); +} +SP_MAIN(run) diff --git a/example/ls.c b/example/ls.c index 203210cc..9b5d8828 100644 --- a/example/ls.c +++ b/example/ls.c @@ -10,7 +10,7 @@ s32 compare_entries(const void* pa, const void* pb) { s32 run(s32 num_args, const c8** args) { sp_mem_heap_t* heap = sp_mem_heap_new(); sp_mem_t mem = sp_mem_heap_as_allocator(heap); - sp_str_t cwd = sp_fs_get_cwd(mem); + sp_str_t cwd = sp_fs_get_cwd_path(mem); sp_str_t dir = cwd; if (num_args == 2) dir = sp_fs_join_path(mem, cwd, sp_str_view(args[1])); diff --git a/example/wc.c b/example/wc.c index 0457231a..884b1e53 100644 --- a/example/wc.c +++ b/example/wc.c @@ -10,7 +10,7 @@ s32 run(s32 num_args, const c8** args) { sp_mem_heap_t* heap = sp_mem_heap_new(); sp_mem_t mem = sp_mem_heap_as_allocator(heap); - sp_str_t cwd = sp_fs_get_cwd(mem); + sp_str_t cwd = sp_fs_get_cwd_path(mem); sp_str_t path = sp_fs_join_path(mem, cwd, sp_str_view(args[1])); sp_str_t content = sp_zero; sp_io_read_file(mem, path, &content); diff --git a/example/zero_copy.c b/example/zero_copy.c index edcd22b5..d3f10bbf 100644 --- a/example/zero_copy.c +++ b/example/zero_copy.c @@ -176,7 +176,7 @@ s32 run(s32 num_args, const c8** args) { sp_mem_heap_t* heap = sp_mem_heap_new(); sp_mem_t mem = sp_mem_heap_as_allocator(heap); - sp_str_t cwd = sp_fs_get_cwd(mem); + sp_str_t cwd = sp_fs_get_cwd_path(mem); sp_str_t src = sp_str_concat(mem, cwd, sp_str_lit("/io.src")); sp_str_t dst = sp_str_concat(mem, cwd, sp_str_lit("/io.dst")); diff --git a/sp.h b/sp.h index 0768c896..4252c144 100644 --- a/sp.h +++ b/sp.h @@ -3044,6 +3044,7 @@ typedef struct { typedef struct { sp_str_t path; + sp_str_t rel; sp_str_t name; sp_fs_kind_t kind; } sp_fs_entry_t; @@ -3053,38 +3054,6 @@ typedef struct { sp_fs_kind_t kind; } sp_fs_dir_entry_t; -typedef struct { - sp_sys_dir_it_t dir; - sp_mem_buffer_t buf; - u64 cursor; -} sp_fs_dir_t; - -typedef struct { - sp_fs_dir_t dir; - SP_ALIGNED u8 buf [SP_FS_IT_BUF_SIZE]; - u32 len; - u32 name_len; -} sp_fs_it_frame_t; - -typedef enum { - SP_FS_IT_ENTRY, - SP_FS_IT_LEAVE, -} sp_fs_it_yield_t; - -typedef struct { - sp_fs_entry_t entry; - sp_path_t at; - sp_fs_it_yield_t yield; - sp_da(sp_fs_it_frame_t) stack; - c8 path [SP_PATH_MAX]; - sp_err_t err; -} sp_fs_it_t; - -#define sp_fs_for(mem, dir, it) \ - for (sp_fs_it_t it = sp_fs_it_new(mem, dir); sp_fs_it_next(&it);) -#define sp_fs_for_recursive(mem, dir, it) \ - for (sp_fs_it_t it = sp_fs_it_new(mem, dir); sp_fs_it_walk(&it);) - SP_API sp_str_t sp_fs_get_name(sp_str_t path); SP_API sp_str_t sp_fs_get_name_for(sp_str_t path, sp_fs_path_kind_t kind); SP_API sp_str_t sp_fs_parent_path(sp_str_t path); @@ -3140,28 +3109,77 @@ SP_API sp_err_t sp_fs_create_sym_link_at(sp_str_t target, sp_path_t SP_API sp_err_t sp_fs_copy_at(sp_path_t from, sp_path_t to, sp_fs_atomic_mode_t mode); SP_API sp_err_t sp_fs_copy_file_at(sp_path_t from, sp_path_t to, sp_fs_atomic_mode_t mode); SP_API sp_err_t sp_fs_copy_tree_at(sp_path_t from, sp_path_t to, sp_fs_atomic_mode_t mode); -SP_API sp_err_t sp_fs_dir_open(sp_fs_dir_t* it, sp_sys_fd_t fd, sp_str_t path, u32 flags, sp_mem_slice_t buf); -SP_API sp_err_t sp_fs_dir_next(sp_fs_dir_t* it, sp_fs_dir_entry_t* out); -SP_API sp_err_t sp_fs_dir_close(sp_fs_dir_t* it); -SP_API sp_fs_it_t sp_fs_it_new(sp_mem_t mem, sp_str_t path); -SP_API sp_fs_it_t sp_fs_it_new_at(sp_mem_t mem, sp_path_t path, u32 flags); -SP_API bool sp_fs_it_next(sp_fs_it_t* it); -SP_API bool sp_fs_it_walk(sp_fs_it_t* it); -SP_API sp_err_t sp_fs_it_enter(sp_fs_it_t* it); -SP_API void sp_fs_it_deinit(sp_fs_it_t* it); // literally sys+normalize, if normalize isnt needed this isn't either? SP_API sp_str_t sp_fs_canonicalize_path(sp_mem_t mem, sp_str_t path); SP_API sp_str_t sp_fs_canonicalize_path_at(sp_mem_t mem, sp_path_t path); // just put these in os -SP_API sp_str_t sp_fs_get_cwd(sp_mem_t mem); +SP_API sp_str_t sp_fs_get_cwd_path(sp_mem_t mem); // these currently forward to sys+normalize; remove from fs AND sys and just put in os SP_API sp_str_t sp_fs_get_exe_path(sp_mem_t mem); SP_API sp_str_t sp_fs_get_storage_path(sp_mem_t mem); SP_API sp_str_t sp_fs_get_config_path(sp_mem_t mem); +/* + sp_fs_it_t + A filesystem iterator + + sp_sys_dir_it_t is the OS level iteration state (usually, just the handle to + the top level directory, but on WASM also a cookie and on macOS a DIR*). The + syscalls are strictly about answering: + - Asking the OS fill a buffer with the next N entries for the handle + - Parsing those opaque bytes into entries + + That can be driven however you want! There's no concept of the "next" entry, + traversing into child directories, or buffer ownership. Syscalls. + + + */ +typedef struct { + sp_sys_dir_it_t dir; + sp_mem_buffer_t buf; + u64 cursor; +} sp_fs_dir_t; + +typedef struct { + sp_fs_dir_t dir; + SP_ALIGNED u8 buf [SP_FS_IT_BUF_SIZE]; + u32 len; + u32 name_len; +} sp_fs_it_frame_t; + +typedef enum { + SP_FS_IT_ENTRY, + SP_FS_IT_LEAVE, +} sp_fs_it_yield_t; + +typedef struct { + sp_fs_entry_t entry; + sp_path_t at; + sp_fs_it_yield_t yield; + sp_da(sp_fs_it_frame_t) stack; + c8 path [SP_PATH_MAX]; + sp_err_t err; +} sp_fs_it_t; + +#define sp_fs_for(mem, dir, it) \ + for (sp_fs_it_t it = sp_fs_it_new(mem, dir); sp_fs_it_next(&it);) +#define sp_fs_for_recursive(mem, dir, it) \ + for (sp_fs_it_t it = sp_fs_it_new(mem, dir); sp_fs_it_walk(&it);) + +SP_API sp_err_t sp_fs_dir_open(sp_fs_dir_t* it, sp_sys_fd_t fd, sp_str_t path, u32 flags, sp_mem_slice_t buf); +SP_API sp_err_t sp_fs_dir_next(sp_fs_dir_t* it, sp_fs_dir_entry_t* out); +SP_API sp_err_t sp_fs_dir_close(sp_fs_dir_t* it); +SP_API sp_fs_it_t sp_fs_it_new(sp_mem_t mem, sp_str_t path); +SP_API sp_fs_it_t sp_fs_it_new_at(sp_mem_t mem, sp_path_t path, u32 flags); +SP_API bool sp_fs_it_next(sp_fs_it_t* it); +SP_API bool sp_fs_it_walk(sp_fs_it_t* it); +SP_API sp_err_t sp_fs_it_enter(sp_fs_it_t* it); +SP_API void sp_fs_it_deinit(sp_fs_it_t* it); + + // ███████████ █████ █████ ███████████ ██████████ █████████ ██████████ █████ ██████ █████ █████████ // ░█░░░███░░░█░░███ ░░███ ░░███░░░░░███ ░░███░░░░░█ ███░░░░░███ ░░███░░░░███ ░░███ ░░██████ ░░███ ███░░░░░███ // ░ ░███ ░ ░███ ░███ ░███ ░███ ░███ █ ░ ░███ ░███ ░███ ░░███ ░███ ░███░███ ░███ ███ ░░░ @@ -20028,7 +20046,7 @@ sp_str_t sp_fs_get_exe_path(sp_mem_t mem) { return sp_str_copy(mem, sp_str(buf, len)); } -sp_str_t sp_fs_get_cwd(sp_mem_t mem) { +sp_str_t sp_fs_get_cwd_path(sp_mem_t mem) { c8 buf[SP_PATH_MAX]; s64 len = sp_sys_get_cwd_path(buf, SP_PATH_MAX); if (len <= 0) return sp_zero_s(sp_str_t); @@ -20174,6 +20192,8 @@ sp_err_t sp_fs_remove_file(sp_str_t path) { sp_err_t sp_fs_dir_open(sp_fs_dir_t* it, sp_sys_fd_t fd, sp_str_t path, u32 flags, sp_mem_slice_t buf) { *it = sp_zero_s(sp_fs_dir_t); + + // @spader wtf? sp_assert(buf.len >= SP_SYS_DIR_MIN_BUF); sp_sys_fd_t dir_fd = SP_SYS_INVALID_FD; @@ -20224,12 +20244,18 @@ SP_PRIVATE void sp_fs_it_unwind(sp_fs_it_t* it) { sp_da_clear(it->stack); } +// @spader this is garbage; we have to control all the trimming and "custom" path +// functions, because it smells like we have no idea what inputs we take +SP_PRIVATE u32 sp_fs_it_child(sp_fs_it_t* it, u32 prefix) { + return prefix + (prefix && !sp_fs_is_sep(it->path[prefix - 1])); +} + SP_PRIVATE sp_err_t sp_fs_it_join(sp_fs_it_t* it, u32 prefix, sp_str_t name, u32* len) { - u32 sep = prefix && !sp_fs_is_sep(it->path[prefix - 1]); - if (prefix + sep + name.len >= SP_PATH_MAX) return SP_ERR_SYS_NAME_TOO_LONG; - if (sep) it->path[prefix] = '/'; - sp_str_copy_to(name, it->path + prefix + sep, name.len); - *len = prefix + sep + name.len; + u32 start = sp_fs_it_child(it, prefix); + if (start + name.len >= SP_PATH_MAX) return SP_ERR_SYS_NAME_TOO_LONG; + if (start > prefix) it->path[prefix] = '/'; + sp_str_copy_to(name, it->path + start, name.len); + *len = start + name.len; return SP_OK; } @@ -20243,6 +20269,7 @@ SP_PRIVATE sp_err_t sp_fs_it_push(sp_fs_it_t* it, sp_path_t path, u32 len, u32 f SP_PRIVATE sp_err_t sp_fs_it_step(sp_fs_it_t* it) { sp_fs_it_frame_t* top = sp_da_back(it->stack); top->dir.buf.data = top->buf; + u32 root = sp_fs_it_child(it, it->stack[0].len); sp_fs_dir_entry_t d = sp_zero; sp_try(sp_fs_dir_next(&top->dir, &d)); @@ -20253,6 +20280,7 @@ SP_PRIVATE sp_err_t sp_fs_it_step(sp_fs_it_t* it) { it->yield = SP_FS_IT_ENTRY; it->entry = (sp_fs_entry_t) { .path = sp_str(it->path, len), + .rel = sp_str(it->path + root, len - root), .name = sp_str(it->path + len - d.name.len, d.name.len), .kind = d.kind, }; @@ -20273,6 +20301,7 @@ SP_PRIVATE sp_err_t sp_fs_it_step(sp_fs_it_t* it) { it->yield = SP_FS_IT_LEAVE; it->entry = (sp_fs_entry_t) { .path = sp_str(it->path, top->len), + .rel = sp_str(it->path + root, top->len - root), .name = sp_str(it->path + top->len - top->name_len, top->name_len), .kind = SP_FS_KIND_DIR, }; @@ -20331,6 +20360,7 @@ SP_PRIVATE sp_fs_entry_t sp_fs_entry_copy(sp_mem_t mem, sp_fs_entry_t entry) { sp_str_t path = sp_str_copy(mem, entry.path); return (sp_fs_entry_t) { .path = path, + .rel = sp_str_suffix(path, (s32)entry.rel.len), .name = sp_str_suffix(path, (s32)entry.name.len), .kind = entry.kind, }; @@ -20668,58 +20698,28 @@ sp_err_t sp_fs_copy_file_at(sp_path_t from, sp_path_t to, sp_fs_atomic_mode_t mo return sp_fs_atomic_commit(&af, mode); } -SP_PRIVATE bool sp_fs_is_same_file(const sp_sys_file_meta_t* a, const sp_sys_file_meta_t* b) { - return a->device == b->device && a->id == b->id; -} - sp_err_t sp_fs_copy_tree_at(sp_path_t from, sp_path_t to, sp_fs_atomic_mode_t mode) { - sp_sys_file_meta_t source = sp_zero; - sp_try(sp_sys_get_path_metadata_s(from.dir, from.sub, &source)); - sp_mem_arena_marker_t s = sp_mem_begin_scratch(); sp_fs_it_t it = sp_fs_it_new_at(s.mem, from, 0); - sp_da(sp_sys_fd_t) dest = sp_da_new(s.mem, sp_sys_fd_t); sp_sys_fd_t fd = SP_SYS_INVALID_FD; - sp_sys_file_meta_t target = sp_zero; sp_err_t err = it.err; if (!err) err = sp_fs_create_dir_at(to); if (!err) err = sp_sys_open_dir_s(to.dir, to.sub, 0, &fd); - if (!err) sp_da_push(dest, fd); - if (!err) err = sp_sys_get_file_metadata(fd, &target); - if (!err && sp_fs_is_same_file(&source, &target)) err = SP_ERR_SYS_INVALID; - - while (!err && sp_fs_it_next(&it)) { - if (it.yield == SP_FS_IT_LEAVE) { - sp_sys_close(*sp_da_back(dest)); - sp_da_pop(dest); - continue; - } - sp_path_t dst = { .dir = *sp_da_back(dest), .sub = it.entry.name }; + while (!err && sp_fs_it_walk(&it)) { + sp_path_t dst = { .dir = fd, .sub = it.entry.rel }; switch (it.entry.kind) { + case SP_FS_KIND_DIR: err = sp_fs_create_dir_at(dst); break; case SP_FS_KIND_FILE: err = sp_fs_copy_file_at(it.at, dst, mode); break; case SP_FS_KIND_SYMLINK: err = sp_fs_copy_link_at(it.at, dst, mode); break; case SP_FS_KIND_NONE: err = SP_ERR_SYS_UNSUPPORTED; break; - case SP_FS_KIND_DIR: { - sp_sys_file_meta_t meta = sp_zero; - err = sp_sys_get_link_metadata_s(it.at.dir, it.at.sub, &meta); - if (err || sp_fs_is_same_file(&meta, &target)) break; - - err = sp_fs_it_enter(&it); - if (!err) err = sp_fs_create_dir_at(dst); - if (!err) err = sp_sys_open_dir_s(dst.dir, dst.sub, 0, &fd); - if (!err) sp_da_push(dest, fd); - break; - } } } if (!err) err = it.err; sp_fs_it_deinit(&it); - sp_da_for(dest, i) { - sp_sys_close(dest[i]); - } + if (fd != SP_SYS_INVALID_FD) sp_sys_close(fd); sp_mem_end_scratch(s); return err; } diff --git a/sp/sp_test.h b/sp/sp_test.h index 3e9348f3..69b877b8 100644 --- a/sp/sp_test.h +++ b/sp/sp_test.h @@ -1072,7 +1072,7 @@ static sp_str_t sp_test_golden_root(sp_test_t* t, sp_str_t file) { sp_mutex_unlock(&runner->mutex); if (!sp_str_empty(root)) return root; - sp_str_t cwd = sp_fs_get_cwd(t->mem); + sp_str_t cwd = sp_fs_get_cwd_path(t->mem); sp_str_t exe_dir = sp_fs_parent_path(sp_fs_get_exe_path(t->mem)); sp_da(sp_str_t) roots = sp_test_resolve_roots(t->mem, cwd, exe_dir); @@ -2101,7 +2101,7 @@ s32 sp_test_main(s32 argc, const c8** argv, const sp_test_entry_t* entries) { if (dir.env) root = sp_cstr_as_str(dir.env); if (dir.opt) root = sp_cstr_as_str(dir.opt); root = sp_fs_normalize_path(runner->mem, root); - if (!sp_fs_is_absolute(root)) root = sp_fs_join_path(runner->mem, sp_fs_get_cwd(runner->mem), root); + if (!sp_fs_is_absolute(root)) root = sp_fs_join_path(runner->mem, sp_fs_get_cwd_path(runner->mem), root); sp_tm_epoch_t now = sp_tm_now_epoch(); sp_tm_epoch_t grace = { .s = now.s - SP_TEST_RUNS_GRACE_S, .ns = now.ns }; diff --git a/spn.toml b/spn.toml index 6fad142d..fabcd951 100644 --- a/spn.toml +++ b/spn.toml @@ -101,6 +101,10 @@ source = ["example/zero_copy.c"] name = "msvc" source = ["example/msvc.c"] +[[example]] +name = "filesystem" +source = ["example/filesystem.c"] + [profile.sanitizer] linkage = "shared" sanitize = ["address", "undefined"] diff --git a/test/fs/canonicalize.c b/test/fs/canonicalize.c index 2b34f4f4..a6346351 100644 --- a/test/fs/canonicalize.c +++ b/test/fs/canonicalize.c @@ -205,6 +205,6 @@ sp_test(fs, canon_dot_resolves_to_cwd) { sp_test_skip_on_wasm() sp_mem_t mem = sp_test_arena(t); - sp_expect_str_eq(t, sp_fs_canonicalize_path(mem, sp_str_lit(".")), sp_fs_get_cwd(mem)); + sp_expect_str_eq(t, sp_fs_canonicalize_path(mem, sp_str_lit(".")), sp_fs_get_cwd_path(mem)); return SP_OK; } diff --git a/test/fs/collect.c b/test/fs/collect.c index 3220bf72..f8631f08 100644 --- a/test/fs/collect.c +++ b/test/fs/collect.c @@ -76,6 +76,7 @@ sp_test_each(fs, collect, test_t, tests) { sp_expect_eq(t, sp_da_size(results), (u64)n); sp_da_for(results, i) { sp_expect_str_eq(t, results[i].name, sp_fs_get_name(results[i].path)); + sp_expect_str_eq(t, sp_fs_join_path(mem, root, results[i].rel), results[i].path); fs_match(t, matches, n, results[i].path, results[i].kind); } fs_match_finish(t, matches, n); diff --git a/test/fs/copy.c b/test/fs/copy.c index 68f6ed06..d2890094 100644 --- a/test/fs/copy.c +++ b/test/fs/copy.c @@ -393,36 +393,6 @@ static const test_t tests [] = { { .path = "E/L", .exists = true, .kind = SP_FS_KIND_SYMLINK, .target = "E/F" }, }, }, - { - .name = "tree_into_itself_across_dirs", - .setup = { - { "A", FS_SETUP_DIR }, - { .path = "A/C", .content = "C" }, - }, - .op = OP_COPY_TREE, - .src = "A", - .dst_dir = "A", - .dst = "B", - .expect = { - { .path = "A/B/C", .exists = true, .kind = SP_FS_KIND_FILE, .content = "C" }, - { .path = "A/B/B" }, - }, - }, - { - .name = "tree_into_itself_nested", - .setup = { - { "A", FS_SETUP_DIR }, - { "A/D", FS_SETUP_DIR }, - { .path = "A/D/C", .content = "C" }, - }, - .op = OP_COPY_TREE, - .src = "A", - .dst = "A/D/E", - .expect = { - { .path = "A/D/E/D/C", .exists = true, .kind = SP_FS_KIND_FILE, .content = "C" }, - { .path = "A/D/E/D/E" }, - }, - }, { .name = "tree_onto_itself", .setup = { @@ -432,7 +402,6 @@ static const test_t tests [] = { .op = OP_COPY_TREE, .src = "A", .dst = "A", - .err = SP_ERR_SYS_INVALID, .expect = { { .path = "A/C", .exists = true, .kind = SP_FS_KIND_FILE, .content = "C" }, }, diff --git a/test/fs/create_dir.c b/test/fs/create_dir.c index fe2467e9..56531bd7 100644 --- a/test/fs/create_dir.c +++ b/test/fs/create_dir.c @@ -153,7 +153,7 @@ sp_test(fs, create_dir_create_multi_level_relative, .serial = true) { sp_mem_t mem = sp_test_arena(t); sp_str_t sandbox = sp_test_dir(t); - sp_str_t cwd = sp_fs_get_cwd(mem); + sp_str_t cwd = sp_fs_get_cwd_path(mem); sp_must_ok(t, sp_sys_chdir_s(sandbox)); sp_err_t result = sp_fs_create_dir(sp_str_lit("A/B")); sp_must_ok(t, sp_sys_chdir_s(cwd)); diff --git a/test/fs/get_cwd.c b/test/fs/get_cwd.c index 1ab131c8..4c79213e 100644 --- a/test/fs/get_cwd.c +++ b/test/fs/get_cwd.c @@ -4,7 +4,7 @@ sp_test(fs, get_cwd_contract) { sp_test_skip_on_wasm() - sp_str_t cwd = sp_fs_get_cwd(sp_test_arena(t)); + sp_str_t cwd = sp_fs_get_cwd_path(sp_test_arena(t)); sp_must(t, sp_fs_is_dir(cwd)); sp_must(t, sp_fs_is_absolute(cwd)); return SP_OK; @@ -16,14 +16,14 @@ sp_test(fs, get_cwd_unlinked_cwd_does_not_leak_deleted_suffix, .serial = true) { return sp_test_skip(t, "unlinked-cwd is a Linux-only scenario"); #else sp_mem_t mem = sp_test_arena(t); - sp_str_t original = sp_fs_get_cwd(mem); + sp_str_t original = sp_fs_get_cwd_path(mem); sp_must_gt(t, original.len, 0); sp_str_t sandbox = sp_test_dir(t); sp_must_ok(t, sp_sys_chdir_s(sandbox)); sp_must_ok(t, sp_fs_remove_dir(sandbox)); - sp_str_t cwd = sp_fs_get_cwd(mem); + sp_str_t cwd = sp_fs_get_cwd_path(mem); // restore cwd before any assertion can fail, so other tests aren't poisoned sp_sys_chdir_s(original); diff --git a/test/fs/it/enter.c b/test/fs/it/enter.c index 0b7102a9..8d25c0ab 100644 --- a/test/fs/it/enter.c +++ b/test/fs/it/enter.c @@ -134,6 +134,7 @@ sp_test_each(fs, it_enter, test_t, tests) { sp_fs_it_t walk = sp_fs_it_new_at(mem, (sp_path_t) { .dir = dir, .sub = base }, 0); while (sp_fs_it_next(&walk)) { + sp_expect_str_eq(t, sp_fs_join_path(mem, base, walk.entry.rel), walk.entry.path); switch (walk.yield) { case SP_FS_IT_LEAVE: { fs_match(t, leaves, num_leaves, walk.entry.path, walk.entry.kind); diff --git a/test/fs/it/enter_sim.c b/test/fs/it/enter_sim.c index 3a0ec299..def4db4b 100644 --- a/test/fs/it/enter_sim.c +++ b/test/fs/it/enter_sim.c @@ -116,6 +116,7 @@ sp_test_each(fs, it_enter_sim, test_t, tests, .serial = true) { sp_expect_str_eq_c(t, walk.entry.path, want->path); sp_expect_str_eq_c(t, walk.entry.name, want->name); sp_expect_str_eq(t, walk.at.sub, walk.entry.name); + sp_expect_str_eq(t, sp_fs_join_path(mem, sp_str_lit("T"), walk.entry.rel), walk.entry.path); sp_expect_eq(t, (u32)walk.entry.kind, (u32)want->kind); sp_expect_eq(t, (u32)walk.yield, (u32)want->yield); } diff --git a/test/fs/it/os.c b/test/fs/it/os.c index fe1835a1..8ffff94e 100644 --- a/test/fs/it/os.c +++ b/test/fs/it/os.c @@ -184,6 +184,7 @@ sp_test_each(fs, it, test_t, tests) { sp_fs_it_t walk = sp_fs_it_new_at(mem, root, 0); while (it->recursive ? sp_fs_it_walk(&walk) : sp_fs_it_next(&walk)) { sp_expect_str_eq(t, walk.entry.name, sp_fs_get_name(walk.entry.path)); + sp_expect_str_eq(t, sp_fs_join_path(mem, base, walk.entry.rel), walk.entry.path); sp_expect_str_eq(t, walk.at.sub, walk.entry.name); sp_expect_eq(t, (u32)sp_fs_get_kind_at(walk.at), (u32)walk.entry.kind); fs_match(t, matches, n, walk.entry.path, walk.entry.kind); diff --git a/test/fs/it/sim.c b/test/fs/it/sim.c index 5eab92d9..f9e97da7 100644 --- a/test/fs/it/sim.c +++ b/test/fs/it/sim.c @@ -202,6 +202,7 @@ sp_test_each(fs, it_sim, test_t, tests, .serial = true) { const entry_t* want = &it->expect.entries[produced]; sp_expect_str_eq_c(t, walk.entry.path, want->path); sp_expect_str_eq_c(t, walk.entry.name, want->name); + sp_expect_str_eq(t, sp_fs_join_path(mem, root, walk.entry.rel), walk.entry.path); sp_expect_eq(t, (u32)walk.entry.kind, (u32)want->kind); } else { diff --git a/test/tools/test.h b/test/tools/test.h index b2728e5c..23f46e7b 100644 --- a/test/tools/test.h +++ b/test/tools/test.h @@ -162,7 +162,7 @@ static sp_str_t sp_test_file_manager_get_top_level(sp_mem_t a) { return sp_test_file_manager_top_level; } - sp_str_t tmp = sp_fs_join_path(a, sp_fs_get_cwd(a), sp_str_lit(".tmp")); + sp_str_t tmp = sp_fs_join_path(a, sp_fs_get_cwd_path(a), sp_str_lit(".tmp")); if (!sp_fs_exists(tmp)) { sp_fs_create_dir(tmp); } diff --git a/tools/sp.c b/tools/sp.c index e2e7b3e4..99fa8b32 100644 --- a/tools/sp.c +++ b/tools/sp.c @@ -383,7 +383,7 @@ static test_t test_init(sp_mem_t mem, tool_t* tool, const target_t* target, sp_s break; } case EXEC_DOCKER: { - sp_str_t mount = sp_fmt(mem, "{}:/sp", sp_fmt_str(sp_fs_get_cwd(mem))).value; + sp_str_t mount = sp_fmt(mem, "{}:/sp", sp_fmt_str(sp_fs_get_cwd_path(mem))).value; sp_str_t path = sp_fmt(mem, "/sp/{}", sp_fmt_str(rel)).value; test.display = sp_fmt(mem, "docker run --rm --platform linux/arm64 -v {} -w /sp debian:stable-slim {}", sp_fmt_str(mount), sp_fmt_str(path)).value; test.config = (sp_ps_config_t) { From 5f2bc921377c38b3fa44e56e410d9cd0ca4cbd86 Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Thu, 24 Sep 2026 15:08:38 -0400 Subject: [PATCH 26/72] sys: raw wasi imports for path and dir syscalls --- sp.h | 132 +++++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 123 insertions(+), 9 deletions(-) diff --git a/sp.h b/sp.h index 4252c144..8cb45e08 100644 --- a/sp.h +++ b/sp.h @@ -6227,6 +6227,11 @@ c8** environ; #define SP_S_ISBLK(m) (((m) & SP_S_IFMT) == SP_S_IFBLK) #define SP_S_ISSOCK(m) (((m) & SP_S_IFMT) == SP_S_IFSOCK) +#if defined(SP_WASM) + #define sp_wasi_import(name) \ + __attribute__((import_module("wasi_snapshot_preview1"), import_name(name))) +#endif + #if defined(SP_WASM_WASI) #include #elif defined(SP_WASM_FREESTANDING) @@ -6244,6 +6249,16 @@ c8** environ; typedef uint16_t __wasi_subclockflags_t; typedef uint8_t __wasi_whence_t; typedef uint32_t __wasi_exitcode_t; + typedef uint64_t __wasi_rights_t; + typedef uint16_t __wasi_oflags_t; + typedef uint32_t __wasi_lookupflags_t; + typedef uint16_t __wasi_fdflags_t; + typedef uint64_t __wasi_dircookie_t; + typedef uint32_t __wasi_dirnamlen_t; + typedef uint64_t __wasi_inode_t; + typedef uint64_t __wasi_device_t; + typedef uint64_t __wasi_linkcount_t; + typedef uint8_t __wasi_filetype_t; typedef struct __wasi_iovec_t { uint8_t* buf; @@ -6294,15 +6309,79 @@ c8** environ; __wasi_subscription_u_t u; } __wasi_subscription_t; - #define __WASI_EVENTTYPE_CLOCK ((__wasi_eventtype_t)0) - #define __WASI_EVENTTYPE_FD_READ ((__wasi_eventtype_t)1) - #define __WASI_EVENTTYPE_FD_WRITE ((__wasi_eventtype_t)2) - #define __WASI_WHENCE_SET ((__wasi_whence_t)0) - #define __WASI_WHENCE_CUR ((__wasi_whence_t)1) - #define __WASI_WHENCE_END ((__wasi_whence_t)2) - - #define sp_wasi_import(name) \ - __attribute__((import_module("wasi_snapshot_preview1"), import_name(name))) + typedef struct __wasi_dirent_t { + __wasi_dircookie_t d_next; + __wasi_inode_t d_ino; + __wasi_dirnamlen_t d_namlen; + __wasi_filetype_t d_type; + } __wasi_dirent_t; + + typedef struct __wasi_filestat_t { + __wasi_device_t dev; + __wasi_inode_t ino; + __wasi_filetype_t filetype; + __wasi_linkcount_t nlink; + __wasi_filesize_t size; + __wasi_timestamp_t atim; + __wasi_timestamp_t mtim; + __wasi_timestamp_t ctim; + } __wasi_filestat_t; + + #define __WASI_EVENTTYPE_CLOCK ((__wasi_eventtype_t)0) + #define __WASI_EVENTTYPE_FD_READ ((__wasi_eventtype_t)1) + #define __WASI_EVENTTYPE_FD_WRITE ((__wasi_eventtype_t)2) + #define __WASI_WHENCE_SET ((__wasi_whence_t)0) + #define __WASI_WHENCE_CUR ((__wasi_whence_t)1) + #define __WASI_WHENCE_END ((__wasi_whence_t)2) + #define __WASI_DIRCOOKIE_START ((__wasi_dircookie_t)0) + #define __WASI_FILETYPE_UNKNOWN ((__wasi_filetype_t)0) + #define __WASI_FILETYPE_BLOCK_DEVICE ((__wasi_filetype_t)1) + #define __WASI_FILETYPE_CHARACTER_DEVICE ((__wasi_filetype_t)2) + #define __WASI_FILETYPE_DIRECTORY ((__wasi_filetype_t)3) + #define __WASI_FILETYPE_REGULAR_FILE ((__wasi_filetype_t)4) + #define __WASI_FILETYPE_SOCKET_DGRAM ((__wasi_filetype_t)5) + #define __WASI_FILETYPE_SOCKET_STREAM ((__wasi_filetype_t)6) + #define __WASI_FILETYPE_SYMBOLIC_LINK ((__wasi_filetype_t)7) + #define __WASI_LOOKUPFLAGS_SYMLINK_FOLLOW ((__wasi_lookupflags_t)(1 << 0)) + #define __WASI_OFLAGS_CREAT ((__wasi_oflags_t)(1 << 0)) + #define __WASI_OFLAGS_DIRECTORY ((__wasi_oflags_t)(1 << 1)) + #define __WASI_OFLAGS_EXCL ((__wasi_oflags_t)(1 << 2)) + #define __WASI_OFLAGS_TRUNC ((__wasi_oflags_t)(1 << 3)) + #define __WASI_FDFLAGS_APPEND ((__wasi_fdflags_t)(1 << 0)) + #define __WASI_FDFLAGS_DSYNC ((__wasi_fdflags_t)(1 << 1)) + #define __WASI_FDFLAGS_NONBLOCK ((__wasi_fdflags_t)(1 << 2)) + #define __WASI_FDFLAGS_RSYNC ((__wasi_fdflags_t)(1 << 3)) + #define __WASI_FDFLAGS_SYNC ((__wasi_fdflags_t)(1 << 4)) + #define __WASI_RIGHTS_FD_DATASYNC ((__wasi_rights_t)(1 << 0)) + #define __WASI_RIGHTS_FD_READ ((__wasi_rights_t)(1 << 1)) + #define __WASI_RIGHTS_FD_SEEK ((__wasi_rights_t)(1 << 2)) + #define __WASI_RIGHTS_FD_FDSTAT_SET_FLAGS ((__wasi_rights_t)(1 << 3)) + #define __WASI_RIGHTS_FD_SYNC ((__wasi_rights_t)(1 << 4)) + #define __WASI_RIGHTS_FD_TELL ((__wasi_rights_t)(1 << 5)) + #define __WASI_RIGHTS_FD_WRITE ((__wasi_rights_t)(1 << 6)) + #define __WASI_RIGHTS_FD_ADVISE ((__wasi_rights_t)(1 << 7)) + #define __WASI_RIGHTS_FD_ALLOCATE ((__wasi_rights_t)(1 << 8)) + #define __WASI_RIGHTS_PATH_CREATE_DIRECTORY ((__wasi_rights_t)(1 << 9)) + #define __WASI_RIGHTS_PATH_CREATE_FILE ((__wasi_rights_t)(1 << 10)) + #define __WASI_RIGHTS_PATH_LINK_SOURCE ((__wasi_rights_t)(1 << 11)) + #define __WASI_RIGHTS_PATH_LINK_TARGET ((__wasi_rights_t)(1 << 12)) + #define __WASI_RIGHTS_PATH_OPEN ((__wasi_rights_t)(1 << 13)) + #define __WASI_RIGHTS_FD_READDIR ((__wasi_rights_t)(1 << 14)) + #define __WASI_RIGHTS_PATH_READLINK ((__wasi_rights_t)(1 << 15)) + #define __WASI_RIGHTS_PATH_RENAME_SOURCE ((__wasi_rights_t)(1 << 16)) + #define __WASI_RIGHTS_PATH_RENAME_TARGET ((__wasi_rights_t)(1 << 17)) + #define __WASI_RIGHTS_PATH_FILESTAT_GET ((__wasi_rights_t)(1 << 18)) + #define __WASI_RIGHTS_PATH_FILESTAT_SET_SIZE ((__wasi_rights_t)(1 << 19)) + #define __WASI_RIGHTS_PATH_FILESTAT_SET_TIMES ((__wasi_rights_t)(1 << 20)) + #define __WASI_RIGHTS_FD_FILESTAT_GET ((__wasi_rights_t)(1 << 21)) + #define __WASI_RIGHTS_FD_FILESTAT_SET_SIZE ((__wasi_rights_t)(1 << 22)) + #define __WASI_RIGHTS_FD_FILESTAT_SET_TIMES ((__wasi_rights_t)(1 << 23)) + #define __WASI_RIGHTS_PATH_SYMLINK ((__wasi_rights_t)(1 << 24)) + #define __WASI_RIGHTS_PATH_REMOVE_DIRECTORY ((__wasi_rights_t)(1 << 25)) + #define __WASI_RIGHTS_PATH_UNLINK_FILE ((__wasi_rights_t)(1 << 26)) + #define __WASI_RIGHTS_POLL_FD_READWRITE ((__wasi_rights_t)(1 << 27)) + #define __WASI_RIGHTS_SOCK_SHUTDOWN ((__wasi_rights_t)(1 << 28)) + #define __WASI_RIGHTS_SOCK_ACCEPT ((__wasi_rights_t)(1 << 29)) sp_wasi_import("fd_read") extern __wasi_errno_t __wasi_fd_read(__wasi_fd_t fd, const __wasi_iovec_t* iovs, __wasi_size_t niov, __wasi_size_t* nread); @@ -6344,6 +6423,41 @@ c8** environ; extern _Noreturn void __wasi_proc_exit(__wasi_exitcode_t code); #endif +#if defined(SP_WASM) + sp_wasi_import("path_open") + extern __wasi_errno_t sp_wasi_path_open(__wasi_fd_t fd, __wasi_lookupflags_t dirflags, const c8* path, __wasi_size_t path_len, __wasi_oflags_t oflags, __wasi_rights_t fs_rights_base, __wasi_rights_t fs_rights_inheriting, __wasi_fdflags_t fdflags, __wasi_fd_t* opened); + + sp_wasi_import("fd_readdir") + extern __wasi_errno_t sp_wasi_fd_readdir(__wasi_fd_t fd, u8* buf, __wasi_size_t buf_len, __wasi_dircookie_t cookie, __wasi_size_t* used); + + sp_wasi_import("path_filestat_get") + extern __wasi_errno_t sp_wasi_path_filestat_get(__wasi_fd_t fd, __wasi_lookupflags_t flags, const c8* path, __wasi_size_t path_len, __wasi_filestat_t* filestat); + + sp_wasi_import("fd_filestat_get") + extern __wasi_errno_t sp_wasi_fd_filestat_get(__wasi_fd_t fd, __wasi_filestat_t* filestat); + + sp_wasi_import("path_create_directory") + extern __wasi_errno_t sp_wasi_path_create_directory(__wasi_fd_t fd, const c8* path, __wasi_size_t path_len); + + sp_wasi_import("path_remove_directory") + extern __wasi_errno_t sp_wasi_path_remove_directory(__wasi_fd_t fd, const c8* path, __wasi_size_t path_len); + + sp_wasi_import("path_unlink_file") + extern __wasi_errno_t sp_wasi_path_unlink_file(__wasi_fd_t fd, const c8* path, __wasi_size_t path_len); + + sp_wasi_import("path_rename") + extern __wasi_errno_t sp_wasi_path_rename(__wasi_fd_t fd, const c8* old_path, __wasi_size_t old_path_len, __wasi_fd_t new_fd, const c8* new_path, __wasi_size_t new_path_len); + + sp_wasi_import("path_link") + extern __wasi_errno_t sp_wasi_path_link(__wasi_fd_t old_fd, __wasi_lookupflags_t old_flags, const c8* old_path, __wasi_size_t old_path_len, __wasi_fd_t new_fd, const c8* new_path, __wasi_size_t new_path_len); + + sp_wasi_import("path_symlink") + extern __wasi_errno_t sp_wasi_path_symlink(const c8* old_path, __wasi_size_t old_path_len, __wasi_fd_t fd, const c8* new_path, __wasi_size_t new_path_len); + + sp_wasi_import("path_readlink") + extern __wasi_errno_t sp_wasi_path_readlink(__wasi_fd_t fd, const c8* path, __wasi_size_t path_len, u8* buf, __wasi_size_t buf_len, __wasi_size_t* used); +#endif + #if defined(SP_WASM) #define SP_WASI_ESUCCESS 0 #define SP_WASI_EACCES 2 From 5e9403a373858179565e2dce6b815d235e999e8c Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Thu, 24 Sep 2026 15:12:35 -0400 Subject: [PATCH 27/72] sys: file metadata on wasi --- sp.h | 40 ++++++++++++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/sp.h b/sp.h index 8cb45e08..548b67a9 100644 --- a/sp.h +++ b/sp.h @@ -7476,6 +7476,26 @@ SP_PRIVATE u32 sp_sys_posix_trim_slashes(const c8* path, u32 len) { } #endif +#if defined(SP_WASM) +SP_PRIVATE sp_sys_timespec_t sp_sys_timespec_from_ns(u64 ns); + +static void sp_sys_file_meta_from_wasi(const __wasi_filestat_t* src, sp_sys_file_meta_t* meta) { + if (src->filetype == __WASI_FILETYPE_SYMBOLIC_LINK) meta->kind = SP_FS_KIND_SYMLINK; + else if (src->filetype == __WASI_FILETYPE_DIRECTORY) meta->kind = SP_FS_KIND_DIR; + else if (src->filetype == __WASI_FILETYPE_REGULAR_FILE) meta->kind = SP_FS_KIND_FILE; + else meta->kind = SP_FS_KIND_NONE; + + meta->size = (s64)src->size; + meta->atime = sp_sys_timespec_from_ns(src->atim); + meta->mtime = sp_sys_timespec_from_ns(src->mtim); + meta->btime = sp_zero_s(sp_sys_timespec_t); + meta->id = src->ino; + meta->device = src->dev; + meta->nlink = src->nlink; + meta->perms = meta->kind == SP_FS_KIND_DIR ? sp_sys_default_dir_perms : sp_sys_default_file_perms; +} +#endif + ////////////////////////////// // SP_SYS_GET_FILE_METADATA // @@ -7551,8 +7571,10 @@ sp_err_t sp_sys_get_file_metadata_p(sp_sys_fd_t fd, sp_sys_file_meta_t* meta) { return SP_OK; #elif defined(SP_WASM) - (void)fd; (void)meta; - return SP_ERR_SYS_UNSUPPORTED; + __wasi_filestat_t native = sp_zero; + sp_try(sp_sys_err_from_wasi(sp_wasi_fd_filestat_get((__wasi_fd_t)fd, &native))); + sp_sys_file_meta_from_wasi(&native, meta); + return SP_OK; #else #error "sp_sys_get_file_metadata" @@ -10375,8 +10397,11 @@ sp_err_t sp_sys_get_path_metadata_p(sp_sys_fd_t fd, const c8* path, u32 len, sp_ return SP_OK; #elif defined(SP_WASM) - (void)fd; (void)path; (void)len; (void)st; - return SP_ERR_SYS_UNSUPPORTED; + __wasi_filestat_t native = sp_zero; + sp_try(sp_sys_err_from_wasi(sp_wasi_path_filestat_get((__wasi_fd_t)fd, __WASI_LOOKUPFLAGS_SYMLINK_FOLLOW, path, len, &native))); + if (path[len - 1] == '/' && native.filetype != __WASI_FILETYPE_DIRECTORY) return SP_ERR_SYS_NOT_DIR; + sp_sys_file_meta_from_wasi(&native, st); + return SP_OK; #else #error "sp_sys_get_path_metadata" @@ -10412,8 +10437,11 @@ sp_err_t sp_sys_get_link_metadata_p(sp_sys_fd_t fd, const c8* path, u32 len, sp_ return SP_OK; #elif defined(SP_WASM) - (void)fd; (void)path; (void)len; (void)st; - return SP_ERR_SYS_UNSUPPORTED; + __wasi_filestat_t native = sp_zero; + sp_try(sp_sys_err_from_wasi(sp_wasi_path_filestat_get((__wasi_fd_t)fd, 0, path, len, &native))); + if (path[len - 1] == '/' && native.filetype != __WASI_FILETYPE_DIRECTORY) return SP_ERR_SYS_NOT_DIR; + sp_sys_file_meta_from_wasi(&native, st); + return SP_OK; #else #error "sp_sys_get_link_metadata" From 4518028f82f82fb3f7a1cd6bca17276beaf70180 Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Thu, 24 Sep 2026 15:14:21 -0400 Subject: [PATCH 28/72] sys: open and open_dir on wasi --- sp.h | 49 +++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 45 insertions(+), 4 deletions(-) diff --git a/sp.h b/sp.h index 548b67a9..58322ad6 100644 --- a/sp.h +++ b/sp.h @@ -8467,6 +8467,36 @@ SP_PRIVATE s32 sp_sys_posix_open_flags(sp_sys_open_mode_t mode, u32 flags) { return o; } +#elif defined(SP_WASM) +#define SP_SYS_WASI_DIR_RIGHTS ( \ + __WASI_RIGHTS_FD_READ | __WASI_RIGHTS_FD_READDIR | __WASI_RIGHTS_FD_FILESTAT_GET | __WASI_RIGHTS_FD_FDSTAT_SET_FLAGS | \ + __WASI_RIGHTS_PATH_OPEN | __WASI_RIGHTS_PATH_CREATE_DIRECTORY | __WASI_RIGHTS_PATH_CREATE_FILE | \ + __WASI_RIGHTS_PATH_LINK_SOURCE | __WASI_RIGHTS_PATH_LINK_TARGET | __WASI_RIGHTS_PATH_SYMLINK | __WASI_RIGHTS_PATH_READLINK | \ + __WASI_RIGHTS_PATH_RENAME_SOURCE | __WASI_RIGHTS_PATH_RENAME_TARGET | \ + __WASI_RIGHTS_PATH_FILESTAT_GET | __WASI_RIGHTS_PATH_FILESTAT_SET_SIZE | __WASI_RIGHTS_PATH_FILESTAT_SET_TIMES | \ + __WASI_RIGHTS_PATH_REMOVE_DIRECTORY | __WASI_RIGHTS_PATH_UNLINK_FILE) + +SP_PRIVATE __wasi_rights_t sp_sys_wasi_open_rights(sp_sys_open_mode_t mode) { + __wasi_rights_t common = __WASI_RIGHTS_FD_SEEK | __WASI_RIGHTS_FD_TELL | __WASI_RIGHTS_FD_ADVISE | __WASI_RIGHTS_FD_FILESTAT_GET | __WASI_RIGHTS_FD_FDSTAT_SET_FLAGS | __WASI_RIGHTS_POLL_FD_READWRITE; + __wasi_rights_t read = __WASI_RIGHTS_FD_READ; + __wasi_rights_t write = __WASI_RIGHTS_FD_WRITE | __WASI_RIGHTS_FD_DATASYNC | __WASI_RIGHTS_FD_SYNC | __WASI_RIGHTS_FD_ALLOCATE | __WASI_RIGHTS_FD_FILESTAT_SET_SIZE | __WASI_RIGHTS_FD_FILESTAT_SET_TIMES; + switch (mode) { + case SP_SYS_OPEN_MODE_RO: return common | read; + case SP_SYS_OPEN_MODE_WO: return common | write; + case SP_SYS_OPEN_MODE_RW: return common | read | write; + case SP_SYS_OPEN_MODE_PATH: return common; + } + return common; +} + +SP_PRIVATE __wasi_oflags_t sp_sys_wasi_open_flags(u32 flags) { + __wasi_oflags_t o = 0; + if (flags & (SP_SYS_OPEN_CREATE | SP_SYS_OPEN_EXCLUSIVE)) o |= __WASI_OFLAGS_CREAT; + if (flags & SP_SYS_OPEN_EXCLUSIVE) o |= __WASI_OFLAGS_EXCL; + if (flags & SP_SYS_OPEN_TRUNCATE) o |= __WASI_OFLAGS_TRUNC; + return o; +} + #endif sp_err_t sp_sys_open_p(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_open_mode_t mode, u32 flags, sp_sys_fd_t* out) { @@ -8512,8 +8542,13 @@ sp_err_t sp_sys_open_p(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_open_mode return SP_OK; #elif defined(SP_WASM) - (void)fd; (void)path; (void)len; (void)mode; (void)flags; - return SP_ERR_SYS_UNSUPPORTED; + __wasi_rights_t rights = sp_sys_wasi_open_rights(mode); + __wasi_fdflags_t fdflags = (flags & SP_SYS_OPEN_APPEND) ? __WASI_FDFLAGS_APPEND : 0; + __wasi_fd_t opened = -1; + sp_try(sp_sys_err_from_wasi(sp_wasi_path_open((__wasi_fd_t)fd, __WASI_LOOKUPFLAGS_SYMLINK_FOLLOW, path, len, sp_sys_wasi_open_flags(flags), rights, rights, fdflags, &opened))); + + *out = (sp_sys_fd_t)opened; + return SP_OK; #else #error "sp_sys_open" @@ -8581,8 +8616,14 @@ sp_err_t sp_sys_open_dir_p(sp_sys_fd_t fd, const c8* path, u32 len, u32 flags, s return SP_OK; #elif defined(SP_WASM) - (void)fd; (void)path; (void)len; (void)flags; - return SP_ERR_SYS_UNSUPPORTED; + __wasi_lookupflags_t lookup = (flags & SP_SYS_OPEN_DIR_NO_FOLLOW) ? 0 : __WASI_LOOKUPFLAGS_SYMLINK_FOLLOW; + __wasi_rights_t inheriting = SP_SYS_WASI_DIR_RIGHTS | sp_sys_wasi_open_rights(SP_SYS_OPEN_MODE_RW); + __wasi_fd_t opened = -1; + __wasi_errno_t err = sp_wasi_path_open((__wasi_fd_t)fd, lookup, path, len, __WASI_OFLAGS_DIRECTORY, SP_SYS_WASI_DIR_RIGHTS, inheriting, 0, &opened); + if (err) return sp_sys_open_dir_nofollow_err(fd, path, len, flags, sp_sys_err_from_wasi(err)); + + *out = (sp_sys_fd_t)opened; + return SP_OK; #else #error "sp_sys_open_dir" From efd77783ef5c2f45b6e63c8b660032ba34a67daf Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Thu, 24 Sep 2026 15:54:54 -0400 Subject: [PATCH 29/72] kill posix slash semantics --- sp.h | 70 ++++++++++++++----------------------------- test/sys/path.c | 79 ++++++++++++------------------------------------- 2 files changed, 42 insertions(+), 107 deletions(-) diff --git a/sp.h b/sp.h index 58322ad6..9bd8302b 100644 --- a/sp.h +++ b/sp.h @@ -5910,12 +5910,16 @@ s64 sp_sys_get_fd_path(sp_sys_fd_t fd, c8* buf, u64 size) { return (sp_rt.vt->get_fd_path)(fd, buf, size); } +SP_PRIVATE u32 sp_sys_trim_path(const c8* path, u32 len) { + return sp_fs_trim_path(sp_str(path, len)).len; +} + sp_err_t sp_sys_open(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_open_mode_t mode, u32 flags, sp_sys_fd_t* out) { - return (sp_rt.vt->open)(fd, path, len, mode, flags, out); + return (sp_rt.vt->open)(fd, path, sp_sys_trim_path(path, len), mode, flags, out); } sp_err_t sp_sys_open_dir(sp_sys_fd_t fd, const c8* path, u32 len, u32 flags, sp_sys_fd_t* out) { - return (sp_rt.vt->open_dir)(fd, path, len, flags, out); + return (sp_rt.vt->open_dir)(fd, path, sp_sys_trim_path(path, len), flags, out); } sp_err_t sp_sys_close(sp_sys_fd_t fd) { @@ -5931,39 +5935,39 @@ sp_err_t sp_sys_pipe_ready(sp_sys_fd_t fd, u8* ready) { } sp_err_t sp_sys_mkdir(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_file_perms_t perms) { - return (sp_rt.vt->mkdir)(fd, path, len, perms); + return (sp_rt.vt->mkdir)(fd, path, sp_sys_trim_path(path, len), perms); } sp_err_t sp_sys_rmdir(sp_sys_fd_t fd, const c8* path, u32 len) { - return (sp_rt.vt->rmdir)(fd, path, len); + return (sp_rt.vt->rmdir)(fd, path, sp_sys_trim_path(path, len)); } sp_err_t sp_sys_unlink(sp_sys_fd_t fd, const c8* path, u32 len) { - return (sp_rt.vt->unlink)(fd, path, len); + return (sp_rt.vt->unlink)(fd, path, sp_sys_trim_path(path, len)); } sp_err_t sp_sys_rename(sp_sys_fd_t from_fd, const c8* from, u32 from_len, sp_sys_fd_t to_fd, const c8* alias, u32 alias_len) { - return (sp_rt.vt->rename)(from_fd, from, from_len, to_fd, alias, alias_len); + return (sp_rt.vt->rename)(from_fd, from, sp_sys_trim_path(from, from_len), to_fd, alias, sp_sys_trim_path(alias, alias_len)); } sp_err_t sp_sys_link(sp_sys_fd_t from, const c8* existing, u32 existing_len, sp_sys_fd_t to, const c8* alias, u32 alias_len) { - return (sp_rt.vt->link)(from, existing, existing_len, to, alias, alias_len); + return (sp_rt.vt->link)(from, existing, sp_sys_trim_path(existing, existing_len), to, alias, sp_sys_trim_path(alias, alias_len)); } sp_err_t sp_sys_symlink(const c8* existing, u32 existing_len, sp_sys_fd_t to_fd, const c8* alias, u32 alias_len) { - return (sp_rt.vt->symlink)(existing, existing_len, to_fd, alias, alias_len); + return (sp_rt.vt->symlink)(existing, existing_len, to_fd, alias, sp_sys_trim_path(alias, alias_len)); } sp_err_t sp_sys_readlink(sp_sys_fd_t fd, const c8* path, u32 len, c8* buf, u64 size, u64* target_len) { - return (sp_rt.vt->readlink)(fd, path, len, buf, size, target_len); + return (sp_rt.vt->readlink)(fd, path, sp_sys_trim_path(path, len), buf, size, target_len); } sp_err_t sp_sys_get_path_metadata(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_file_meta_t* st) { - return (sp_rt.vt->get_path_metadata)(fd, path, len, st); + return (sp_rt.vt->get_path_metadata)(fd, path, sp_sys_trim_path(path, len), st); } sp_err_t sp_sys_get_link_metadata(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_file_meta_t* st) { - return (sp_rt.vt->get_link_metadata)(fd, path, len, st); + return (sp_rt.vt->get_link_metadata)(fd, path, sp_sys_trim_path(path, len), st); } sp_err_t sp_sys_get_file_metadata(sp_sys_fd_t fd, sp_sys_file_meta_t* st) { @@ -5971,11 +5975,11 @@ sp_err_t sp_sys_get_file_metadata(sp_sys_fd_t fd, sp_sys_file_meta_t* st) { } sp_err_t sp_sys_set_file_perms(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_file_perms_t perms) { - return (sp_rt.vt->set_file_perms)(fd, path, len, perms); + return (sp_rt.vt->set_file_perms)(fd, path, sp_sys_trim_path(path, len), perms); } sp_err_t sp_sys_set_times(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_timespec_t atime, sp_sys_timespec_t mtime) { - return (sp_rt.vt->set_times)(fd, path, len, atime, mtime); + return (sp_rt.vt->set_times)(fd, path, sp_sys_trim_path(path, len), atime, mtime); } sp_err_t sp_sys_clock_gettime(s32 clockid, sp_sys_timespec_t* ts) { @@ -6163,7 +6167,7 @@ s64 sp_sys_lseek(sp_sys_fd_t fd, s64 offset, s32 whence) { } sp_err_t sp_sys_chdir(const c8* path, u32 len) { - return (sp_rt.vt->chdir)(path, len); + return (sp_rt.vt->chdir)(path, sp_sys_trim_path(path, len)); } sp_err_t sp_sys_dir_it_open(sp_sys_fd_t fd, sp_sys_dir_it_t* out) { @@ -7296,7 +7300,6 @@ SP_PRIVATE sp_err_t sp_sys_nt_delete(sp_sys_fd_t fd, sp_str_t path, bool dir) { // We have to conform to the lowest common denominator, which in this case // is POSIX. That means that unlink() is for everything except directories, // and rmdir is only for directories. - path = sp_fs_trim_path(path); u32 options = SP_NT_FILE_SYNCHRONOUS_IO_NONALERT | SP_NT_FILE_OPEN_REPARSE_POINT; if (dir) options |= SP_NT_FILE_DIRECTORY_FILE; @@ -7430,11 +7433,8 @@ static void sp_sys_timespec_from_filetime(FILETIME ft, sp_sys_timespec_t* out) { } static sp_err_t sp_sys_file_meta_from_nt_path(sp_sys_fd_t dir, sp_str_t path, sp_sys_file_meta_t* out, bool follow_symlinks) { - sp_str_t trimmed = sp_fs_trim_path(path); - bool trailing = trimmed.len != path.len; - path = trimmed; u32 options = SP_NT_FILE_SYNCHRONOUS_IO_NONALERT | SP_NT_FILE_OPEN_FOR_BACKUP_INTENT; - if (!follow_symlinks && !trailing) options |= SP_NT_FILE_OPEN_REPARSE_POINT; + if (!follow_symlinks) options |= SP_NT_FILE_OPEN_REPARSE_POINT; sp_sys_fd_t fd = SP_SYS_INVALID_FD; sp_nt_status_t status = sp_sys_nt_open( dir, @@ -7446,7 +7446,6 @@ static sp_err_t sp_sys_file_meta_from_nt_path(sp_sys_fd_t dir, sp_str_t path, sp if (!SP_NT_SUCCESS(status)) return sp_sys_err_from_nt(status); sp_err_t rc = sp_sys_get_file_metadata_p(fd, out); sp_sys_nt_close(fd); - if (!rc && trailing && out->kind != SP_FS_KIND_DIR) return SP_ERR_SYS_NOT_DIR; return rc; } @@ -7469,11 +7468,6 @@ SP_PRIVATE sp_err_t sp_sys_tty_err_from_win32(DWORD err) { SP_PRIVATE size_t sp_sys_posix_io_count(u64 count) { return count > SP_SYS_POSIX_IO_MAX ? SP_SYS_POSIX_IO_MAX : (size_t)count; } - -SP_PRIVATE u32 sp_sys_posix_trim_slashes(const c8* path, u32 len) { - while (len > 1 && path[len - 1] == '/') len--; - return len; -} #endif #if defined(SP_WASM) @@ -10433,14 +10427,12 @@ sp_err_t sp_sys_get_path_metadata_p(sp_sys_fd_t fd, const c8* path, u32 len, sp_ sp_try(sp_sys_posix_path(path, len, buf)); struct stat native; sp_try(sp_sys_err_from_libc(fstatat(fd, buf, &native, 0))); - if (path[len - 1] == '/' && !S_ISDIR(native.st_mode)) return SP_ERR_SYS_NOT_DIR; sp_sys_file_meta_from_libc(&native, st); return SP_OK; #elif defined(SP_WASM) __wasi_filestat_t native = sp_zero; sp_try(sp_sys_err_from_wasi(sp_wasi_path_filestat_get((__wasi_fd_t)fd, __WASI_LOOKUPFLAGS_SYMLINK_FOLLOW, path, len, &native))); - if (path[len - 1] == '/' && native.filetype != __WASI_FILETYPE_DIRECTORY) return SP_ERR_SYS_NOT_DIR; sp_sys_file_meta_from_wasi(&native, st); return SP_OK; @@ -10473,14 +10465,12 @@ sp_err_t sp_sys_get_link_metadata_p(sp_sys_fd_t fd, const c8* path, u32 len, sp_ sp_try(sp_sys_posix_path(path, len, buf)); struct stat native; sp_try(sp_sys_err_from_libc(fstatat(fd, buf, &native, AT_SYMLINK_NOFOLLOW))); - if (path[len - 1] == '/' && !S_ISDIR(native.st_mode)) return SP_ERR_SYS_NOT_DIR; sp_sys_file_meta_from_libc(&native, st); return SP_OK; #elif defined(SP_WASM) __wasi_filestat_t native = sp_zero; sp_try(sp_sys_err_from_wasi(sp_wasi_path_filestat_get((__wasi_fd_t)fd, 0, path, len, &native))); - if (path[len - 1] == '/' && native.filetype != __WASI_FILETYPE_DIRECTORY) return SP_ERR_SYS_NOT_DIR; sp_sys_file_meta_from_wasi(&native, st); return SP_OK; @@ -10521,7 +10511,7 @@ sp_err_t sp_sys_mkdir_p(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_file_per #elif defined(SP_MACOS) || defined(SP_COSMO) c8 buf [SP_PATH_MAX]; - sp_try(sp_sys_posix_path(path, sp_sys_posix_trim_slashes(path, len), buf)); + sp_try(sp_sys_posix_path(path, len, buf)); return sp_sys_err_from_libc(mkdirat(fd, buf, (mode_t)(perms.value & 07777))); #elif defined(SP_WASM) @@ -10551,7 +10541,7 @@ sp_err_t sp_sys_rmdir_p(sp_sys_fd_t fd, const c8* path, u32 len) { #elif defined(SP_MACOS) || defined(SP_COSMO) c8 buf [SP_PATH_MAX]; - sp_try(sp_sys_posix_path(path, sp_sys_posix_trim_slashes(path, len), buf)); + sp_try(sp_sys_posix_path(path, len, buf)); return sp_sys_err_from_libc(unlinkat(fd, buf, AT_REMOVEDIR)); #elif defined(SP_WASM) @@ -10572,14 +10562,7 @@ sp_err_t sp_sys_rmdir_s(sp_sys_fd_t fd, sp_str_t path) { /////////////////// sp_err_t sp_sys_unlink_p(sp_sys_fd_t fd, const c8* path, u32 len) { #if defined(SP_WIN32) - sp_str_t full = sp_str(path, len); - sp_str_t trimmed = sp_fs_trim_path(full); - if (trimmed.len != full.len) { - sp_sys_file_meta_t meta = sp_zero; - sp_try(sp_sys_file_meta_from_nt_path(fd, trimmed, &meta, false)); - return meta.kind == SP_FS_KIND_DIR ? SP_ERR_SYS_IS_DIR : SP_ERR_SYS_NOT_DIR; - } - return sp_sys_nt_delete(fd, full, false); + return sp_sys_nt_delete(fd, sp_str(path, len), false); #elif defined(SP_LINUX) c8 buf [SP_PATH_MAX]; @@ -10588,14 +10571,7 @@ sp_err_t sp_sys_unlink_p(sp_sys_fd_t fd, const c8* path, u32 len) { #elif defined(SP_MACOS) || defined(SP_COSMO) c8 buf [SP_PATH_MAX]; - u32 n = sp_sys_posix_trim_slashes(path, len); - sp_try(sp_sys_posix_path(path, n, buf)); - - if (n != len) { - struct stat st; - sp_try(sp_sys_err_from_libc(fstatat(fd, buf, &st, AT_SYMLINK_NOFOLLOW))); - return S_ISDIR(st.st_mode) ? SP_ERR_SYS_IS_DIR : SP_ERR_SYS_NOT_DIR; - } + sp_try(sp_sys_posix_path(path, len, buf)); s32 rc = unlinkat(fd, buf, 0); if (rc < 0 && errno == SP_EPERM) { diff --git a/test/sys/path.c b/test/sys/path.c index 68fc3442..5d7fe42c 100644 --- a/test/sys/path.c +++ b/test/sys/path.c @@ -133,9 +133,8 @@ static const test_t slash_tests [] = { .name = "unlink_slash_file", .op = OP_UNLINK, .path = "file.bin/", - .err = SP_ERR_SYS_NOT_DIR, .file = "file.bin", - .exists = "file.bin", + .not_exists = "file.bin", }, { .name = "unlink_slash_dir", @@ -145,34 +144,23 @@ static const test_t slash_tests [] = { .dir = "dir", .exists = "dir", }, - { - .name = "unlink_slash_file_link", - .op = OP_UNLINK, - .path = "lnk/", - .err = SP_ERR_SYS_NOT_DIR, - .file = "file.bin", - .link = "lnk", - .target = "file.bin", - .exists = "file.bin", - }, { .name = "unlink_slash_dir_link", .op = OP_UNLINK, .path = "lnk/", - .err = SP_ERR_SYS_NOT_DIR, .dir = "dir", .link = "lnk", .target = "dir", - .exists = "lnk", + .exists = "dir", + .not_exists = "lnk", }, { .name = "unlink_slash_dangling_link", .op = OP_UNLINK, .path = "lnk/", - .err = SP_ERR_SYS_NOT_DIR, .link = "lnk", .target = "missing", - .exists = "lnk", + .not_exists = "lnk", }, { .name = "rmdir_slash_dir", @@ -182,22 +170,19 @@ static const test_t slash_tests [] = { .not_exists = "dir", }, { - .name = "rmdir_slash_file", + .name = "rmdir_double_slash_dir", .op = OP_RMDIR, - .path = "file.bin/", - .err = SP_ERR_SYS_NOT_DIR, - .file = "file.bin", - .exists = "file.bin", + .path = "dir//", + .dir = "dir", + .not_exists = "dir", }, { - .name = "rmdir_slash_file_link", + .name = "rmdir_slash_file", .op = OP_RMDIR, - .path = "lnk/", + .path = "file.bin/", .err = SP_ERR_SYS_NOT_DIR, .file = "file.bin", - .link = "lnk", - .target = "file.bin", - .exists = "lnk", + .exists = "file.bin", }, { .name = "rmdir_slash_dir_link", @@ -228,8 +213,8 @@ static const test_t slash_tests [] = { .name = "stat_slash_file", .op = OP_STAT, .path = "file.bin/", - .err = SP_ERR_SYS_NOT_DIR, .file = "file.bin", + .kind = SP_FS_KIND_FILE, }, { .name = "stat_slash_dir", @@ -242,19 +227,10 @@ static const test_t slash_tests [] = { .name = "stat_slash_file_link", .op = OP_STAT, .path = "lnk/", - .err = SP_ERR_SYS_NOT_DIR, .file = "file.bin", .link = "lnk", .target = "file.bin", - }, - { - .name = "stat_slash_dir_link", - .op = OP_STAT, - .path = "lnk/", - .dir = "dir", - .link = "lnk", - .target = "dir", - .kind = SP_FS_KIND_DIR, + .kind = SP_FS_KIND_FILE, }, { .name = "stat_slash_dangling_link", @@ -274,24 +250,8 @@ static const test_t slash_tests [] = { .name = "lstat_slash_file", .op = OP_LSTAT, .path = "file.bin/", - .err = SP_ERR_SYS_NOT_DIR, .file = "file.bin", - }, - { - .name = "lstat_slash_dir", - .op = OP_LSTAT, - .path = "dir/", - .dir = "dir", - .kind = SP_FS_KIND_DIR, - }, - { - .name = "lstat_slash_file_link", - .op = OP_LSTAT, - .path = "lnk/", - .err = SP_ERR_SYS_NOT_DIR, - .file = "file.bin", - .link = "lnk", - .target = "file.bin", + .kind = SP_FS_KIND_FILE, }, { .name = "lstat_slash_dir_link", @@ -300,24 +260,23 @@ static const test_t slash_tests [] = { .dir = "dir", .link = "lnk", .target = "dir", - .kind = SP_FS_KIND_DIR, + .kind = SP_FS_KIND_SYMLINK, }, { .name = "lstat_slash_dangling_link", .op = OP_LSTAT, .path = "lnk/", - .err = SP_ERR_SYS_NOT_FOUND, .link = "lnk", .target = "missing", + .kind = SP_FS_KIND_SYMLINK, }, { .name = "unlink_slash_file_abs", .op = OP_UNLINK, .path_kind = PATH_ABS, .path = "file.bin/", - .err = SP_ERR_SYS_NOT_DIR, .file = "file.bin", - .exists = "file.bin", + .not_exists = "file.bin", }, { .name = "rmdir_slash_dir_abs", @@ -339,8 +298,8 @@ static const test_t slash_tests [] = { .op = OP_STAT, .path_kind = PATH_ABS, .path = "file.bin/", - .err = SP_ERR_SYS_NOT_DIR, .file = "file.bin", + .kind = SP_FS_KIND_FILE, }, { .name = "lstat_slash_dir_link_abs", @@ -350,7 +309,7 @@ static const test_t slash_tests [] = { .dir = "dir", .link = "lnk", .target = "dir", - .kind = SP_FS_KIND_DIR, + .kind = SP_FS_KIND_SYMLINK, }, }; From 16fdc7094dfca5759556d345bbd01a535539d4e4 Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Fri, 25 Sep 2026 01:55:45 -0400 Subject: [PATCH 30/72] cwd stuff --- example/filesystem.c | 25 ++++++++++++----------- sp.h | 47 ++++++++++++++++++++++++++++++++++++++++++-- test/fs/get_cwd.c | 47 ++++++++++++++++++++++++++++++++++++++++++++ test/sys/root.c | 13 ++++++++++++ 4 files changed, 118 insertions(+), 14 deletions(-) diff --git a/example/filesystem.c b/example/filesystem.c index 0fc1239b..6be573fe 100644 --- a/example/filesystem.c +++ b/example/filesystem.c @@ -25,12 +25,16 @@ void print_entry(sp_fs_entry_t entry, u32 depth) { } -void iterate(sp_path_t path) { +void report(sp_str_t dir, sp_err_t err) { + if (err) sp_log("{.red}: {}", sp_fmt_str(dir), sp_fmt_str(sp_err_str(err))); +} + +void iterate(sp_str_t dir, sp_path_t path) { sp_mem_arena_marker_t s = sp_mem_begin_scratch(); print_entry((sp_fs_entry_t) { .kind = SP_FS_KIND_DIR, - .name = sp_fs_get_stem(path.sub) + .name = sp_fs_get_name(dir) }, 0); sp_fs_it_t it = sp_fs_it_new_at(s.mem, path, 0); @@ -47,15 +51,17 @@ void iterate(sp_path_t path) { // case SP_FS_KIND_NONE: break; // } } + report(dir, it.err); sp_mem_end_scratch(s); } -void walk(sp_path_t path) { +void walk(sp_str_t dir, sp_path_t path) { sp_mem_arena_marker_t s = sp_mem_begin_scratch(); sp_fs_it_t it = sp_fs_it_new_at(s.mem, path, 0); while (sp_fs_it_walk(&it)) { print_entry(it.entry, sp_da_size(it.stack) - 1); } + report(dir, it.err); sp_mem_end_scratch(s); } @@ -63,18 +69,13 @@ sp_cli_result_t command(sp_cli_t* cli) { ctx_t* ctx = sp_ptr_cast(ctx_t*, cli->user_data); args_t args = ctx->args; - sp_path_t path = { - .dir = sp_sys_get_root(0), - .sub = sp_str_empty(args.dir) ? - sp_str_lit(".") : - args.dir - }; - sp_log("{}", sp_fmt_int(path.dir)); + sp_str_t dir = sp_str_empty(args.dir) ? sp_str_lit(".") : args.dir; + sp_path_t path = sp_path_at_root(dir); if (args.walk) { - walk(path); + walk(dir, path); } else { - iterate(path); + iterate(dir, path); } return SP_CLI_OK; } diff --git a/sp.h b/sp.h index 9bd8302b..395c98c6 100644 --- a/sp.h +++ b/sp.h @@ -1454,6 +1454,7 @@ SP_API sp_err_t sp_sys_pwrite(sp_sys_fd_t fd, const void* buf, u64 count, u64 SP_API sp_err_t sp_sys_transfer(sp_sys_fd_t in, u64* in_pos, sp_sys_fd_t out, u64 count, u64* bytes_moved); SP_API sp_err_t sp_sys_transfer_positional(sp_sys_fd_t in, u64* in_pos, sp_sys_fd_t out, u64 count, u64 offset, u64* bytes_moved); SP_API sp_sys_fd_t sp_sys_get_root(s32 it); +SP_API s64 sp_sys_get_root_name(s32 it, c8* buf, u64 size); SP_API s64 sp_sys_get_exe_path(c8* buf, u64 size); SP_API s64 sp_sys_get_cwd_path(c8* buf, u64 size); SP_API s64 sp_sys_get_storage_path(c8* buf, u64 size); @@ -1553,6 +1554,7 @@ typedef struct { sp_err_t (*transfer)(sp_sys_fd_t in, u64* in_pos, sp_sys_fd_t out, u64 count, u64* bytes_moved); sp_err_t (*transfer_positional)(sp_sys_fd_t in, u64* in_pos, sp_sys_fd_t out, u64 count, u64 offset, u64* bytes_moved); sp_sys_fd_t (*get_root)(s32 it); + s64 (*get_root_name)(s32 it, c8* buf, u64 size); s64 (*get_exe_path)(c8* buf, u64 size); s64 (*get_cwd_path)(c8* buf, u64 size); s64 (*get_storage_path)(c8* buf, u64 size); @@ -1628,6 +1630,7 @@ SP_API sp_err_t sp_sys_pwrite_p(sp_sys_fd_t fd, const void* buf, u64 count, u SP_API sp_err_t sp_sys_transfer_p(sp_sys_fd_t in, u64* in_pos, sp_sys_fd_t out, u64 count, u64* bytes_moved); SP_API sp_err_t sp_sys_transfer_positional_p(sp_sys_fd_t in, u64* in_pos, sp_sys_fd_t out, u64 count, u64 offset, u64* bytes_moved); SP_API sp_sys_fd_t sp_sys_get_root_p(s32 it); +SP_API s64 sp_sys_get_root_name_p(s32 it, c8* buf, u64 size); SP_API s64 sp_sys_get_exe_path_p(c8* buf, u64 size); SP_API s64 sp_sys_get_cwd_path_p(c8* buf, u64 size); SP_API s64 sp_sys_get_storage_path_p(c8* buf, u64 size); @@ -3109,6 +3112,9 @@ SP_API sp_err_t sp_fs_create_sym_link_at(sp_str_t target, sp_path_t SP_API sp_err_t sp_fs_copy_at(sp_path_t from, sp_path_t to, sp_fs_atomic_mode_t mode); SP_API sp_err_t sp_fs_copy_file_at(sp_path_t from, sp_path_t to, sp_fs_atomic_mode_t mode); SP_API sp_err_t sp_fs_copy_tree_at(sp_path_t from, sp_path_t to, sp_fs_atomic_mode_t mode); +SP_API sp_sys_fd_t sp_fs_get_cwd(void); +SP_API sp_sys_fd_t sp_fs_get_root(sp_str_t name); +SP_API sp_path_t sp_path_at_root(sp_str_t sub); // literally sys+normalize, if normalize isnt needed this isn't either? SP_API sp_str_t sp_fs_canonicalize_path(sp_mem_t mem, sp_str_t path); @@ -5106,7 +5112,6 @@ SP_IMP BOOL CALLBACK sp_tls_once_trampoline(PINIT_ONCE once, PVOID param, PVOI #endif // @fs -SP_IMP sp_path_t sp_path_at_root(sp_str_t sub); SP_IMP sp_fs_kind_t sp_fs_get_link_kind(sp_str_t path); SP_IMP sp_fs_kind_t sp_fs_get_link_kind_at(sp_path_t path); @@ -5716,6 +5721,7 @@ const sp_sys_vtable_t sp_sys_vtable_platform = { .transfer = sp_sys_transfer_p, .transfer_positional = sp_sys_transfer_positional_p, .get_root = sp_sys_get_root_p, + .get_root_name = sp_sys_get_root_name_p, .get_exe_path = sp_sys_get_exe_path_p, .get_cwd_path = sp_sys_get_cwd_path_p, .get_storage_path = sp_sys_get_storage_path_p, @@ -5890,6 +5896,10 @@ sp_sys_fd_t sp_sys_get_root(s32 it) { return (sp_rt.vt->get_root)(it); } +s64 sp_sys_get_root_name(s32 it, c8* buf, u64 size) { + return (sp_rt.vt->get_root_name)(it, buf, size); +} + s64 sp_sys_get_exe_path(c8* buf, u64 size) { return (sp_rt.vt->get_exe_path)(buf, size); } @@ -8655,6 +8665,22 @@ sp_sys_fd_t sp_sys_get_root_p(s32 it) { #endif } +s64 sp_sys_get_root_name_p(s32 it, c8* buf, u64 size) { +#if defined(SP_WIN32) || defined(SP_LINUX) || defined(SP_MACOS) || defined(SP_COSMO) + if (it != 0 || !buf || size < 2) return -1; + buf[0] = '.'; + buf[1] = 0; + return 1; + +#elif defined(SP_WASM) + (void)it; (void)buf; (void)size; + return -1; + +#else + #error "sp_sys_get_root_name" +#endif +} + ////////////////////// // SP_SYS_NANOSLEEP // ////////////////////// @@ -20080,8 +20106,25 @@ sp_str_t sp_fs_replace_ext(sp_mem_t mem, sp_str_t path, sp_str_t ext) { sp_str_join(mem, stripped, ext, sp_str_lit(".")); } +sp_sys_fd_t sp_fs_get_root(sp_str_t name) { + name = sp_fs_trim_path(name); + for (s32 it = 0;; it++) { + sp_sys_fd_t fd = sp_sys_get_root(it); + if (fd == SP_SYS_INVALID_FD) return SP_SYS_INVALID_FD; + + c8 buf [SP_PATH_MAX]; + s64 len = sp_sys_get_root_name(it, buf, SP_PATH_MAX); + if (len < 0) return SP_SYS_INVALID_FD; + if (sp_str_equal(sp_fs_trim_path(sp_str(buf, len)), name)) return fd; + } +} + +sp_sys_fd_t sp_fs_get_cwd(void) { + return sp_fs_get_root(sp_str_lit(".")); +} + sp_path_t sp_path_at_root(sp_str_t sub) { - return (sp_path_t) { .dir = sp_sys_get_root(0), .sub = sub }; + return (sp_path_t) { .dir = sp_fs_get_cwd(), .sub = sub }; } sp_fs_kind_t sp_fs_get_link_kind_at(sp_path_t path) { diff --git a/test/fs/get_cwd.c b/test/fs/get_cwd.c index 4c79213e..60523cbf 100644 --- a/test/fs/get_cwd.c +++ b/test/fs/get_cwd.c @@ -32,3 +32,50 @@ sp_test(fs, get_cwd_unlinked_cwd_does_not_leak_deleted_suffix, .serial = true) { return SP_OK; #endif } + +typedef struct { + const c8* name; + bool found; +} fs_get_root_t; + +static sp_err_t run_get_root(sp_test_t* t, fs_get_root_t c) { + sp_sys_fd_t fd = sp_fs_get_root(sp_cstr_as_str(c.name)); + sp_expect_eq(t, fd != SP_SYS_INVALID_FD, c.found); + if (c.found) sp_expect_eq(t, fd, sp_fs_get_cwd()); + return SP_OK; +} + +sp_test(fs, get_root_by_name) { + sp_test_skip_on_wasm() + + fs_get_root_t cases [] = { + { .name = ".", .found = true }, + { .name = "./", .found = true }, + { .name = "" }, + { .name = ".." }, + { .name = "/" }, + { .name = "/config" }, + }; + sp_carr_for(cases, it) sp_try(run_get_root(t, cases[it])); + return SP_OK; +} + +sp_test(fs, get_cwd_is_root_zero) { + sp_test_skip_on_wasm() + sp_expect_eq(t, sp_fs_get_cwd(), sp_sys_get_root(0)); + return SP_OK; +} + +sp_test(fs, path_at_root_resolves_against_cwd) { + sp_test_skip_on_wasm() + + sp_str_t file = sp_fs_join_path(sp_test_arena(t), sp_test_dir(t), sp_str_lit("A")); + sp_must_ok(t, sp_fs_create_file(file)); + + sp_path_t path = sp_path_at_root(file); + sp_expect_eq(t, path.dir, sp_fs_get_cwd()); + sp_expect_str_eq(t, path.sub, file); + sp_expect(t, sp_fs_is_file_at(path)); + sp_expect(t, sp_fs_is_dir_at(sp_path_at_root(sp_str_lit(".")))); + return SP_OK; +} diff --git a/test/sys/root.c b/test/sys/root.c index 7477ea90..6f2810f3 100644 --- a/test/sys/root.c +++ b/test/sys/root.c @@ -6,3 +6,16 @@ UTEST_F(sys_root, out_of_range_returns_invalid) { SKIP_ON_WASM() EXPECT_EQ(sp_sys_get_root(1024), SP_SYS_INVALID_FD); } + +UTEST_F(sys_root, root_zero_is_named_dot) { + SKIP_ON_WASM() + c8 buf [8] = sp_zero; + EXPECT_EQ(sp_sys_get_root_name(0, buf, sizeof(buf)), 1); + EXPECT_STREQ(buf, "."); +} + +UTEST_F(sys_root, out_of_range_has_no_name) { + SKIP_ON_WASM() + c8 buf [8] = sp_zero; + EXPECT_EQ(sp_sys_get_root_name(1024, buf, sizeof(buf)), -1); +} From 276a6dc246fb7b97bbb6321c7fa6e0bc5411b66c Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Sat, 26 Sep 2026 14:22:49 -0400 Subject: [PATCH 31/72] kek --- sp.h | 54 ++++++++++++++++++++++++++++++++---------------------- 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/sp.h b/sp.h index 395c98c6..47a129df 100644 --- a/sp.h +++ b/sp.h @@ -5920,16 +5920,12 @@ s64 sp_sys_get_fd_path(sp_sys_fd_t fd, c8* buf, u64 size) { return (sp_rt.vt->get_fd_path)(fd, buf, size); } -SP_PRIVATE u32 sp_sys_trim_path(const c8* path, u32 len) { - return sp_fs_trim_path(sp_str(path, len)).len; -} - sp_err_t sp_sys_open(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_open_mode_t mode, u32 flags, sp_sys_fd_t* out) { - return (sp_rt.vt->open)(fd, path, sp_sys_trim_path(path, len), mode, flags, out); + return (sp_rt.vt->open)(fd, path, len, mode, flags, out); } sp_err_t sp_sys_open_dir(sp_sys_fd_t fd, const c8* path, u32 len, u32 flags, sp_sys_fd_t* out) { - return (sp_rt.vt->open_dir)(fd, path, sp_sys_trim_path(path, len), flags, out); + return (sp_rt.vt->open_dir)(fd, path, len, flags, out); } sp_err_t sp_sys_close(sp_sys_fd_t fd) { @@ -5945,39 +5941,39 @@ sp_err_t sp_sys_pipe_ready(sp_sys_fd_t fd, u8* ready) { } sp_err_t sp_sys_mkdir(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_file_perms_t perms) { - return (sp_rt.vt->mkdir)(fd, path, sp_sys_trim_path(path, len), perms); + return (sp_rt.vt->mkdir)(fd, path, len, perms); } sp_err_t sp_sys_rmdir(sp_sys_fd_t fd, const c8* path, u32 len) { - return (sp_rt.vt->rmdir)(fd, path, sp_sys_trim_path(path, len)); + return (sp_rt.vt->rmdir)(fd, path, len); } sp_err_t sp_sys_unlink(sp_sys_fd_t fd, const c8* path, u32 len) { - return (sp_rt.vt->unlink)(fd, path, sp_sys_trim_path(path, len)); + return (sp_rt.vt->unlink)(fd, path, len); } sp_err_t sp_sys_rename(sp_sys_fd_t from_fd, const c8* from, u32 from_len, sp_sys_fd_t to_fd, const c8* alias, u32 alias_len) { - return (sp_rt.vt->rename)(from_fd, from, sp_sys_trim_path(from, from_len), to_fd, alias, sp_sys_trim_path(alias, alias_len)); + return (sp_rt.vt->rename)(from_fd, from, from_len, to_fd, alias, alias_len); } sp_err_t sp_sys_link(sp_sys_fd_t from, const c8* existing, u32 existing_len, sp_sys_fd_t to, const c8* alias, u32 alias_len) { - return (sp_rt.vt->link)(from, existing, sp_sys_trim_path(existing, existing_len), to, alias, sp_sys_trim_path(alias, alias_len)); + return (sp_rt.vt->link)(from, existing, existing_len, to, alias, alias_len); } sp_err_t sp_sys_symlink(const c8* existing, u32 existing_len, sp_sys_fd_t to_fd, const c8* alias, u32 alias_len) { - return (sp_rt.vt->symlink)(existing, existing_len, to_fd, alias, sp_sys_trim_path(alias, alias_len)); + return (sp_rt.vt->symlink)(existing, existing_len, to_fd, alias, alias_len); } sp_err_t sp_sys_readlink(sp_sys_fd_t fd, const c8* path, u32 len, c8* buf, u64 size, u64* target_len) { - return (sp_rt.vt->readlink)(fd, path, sp_sys_trim_path(path, len), buf, size, target_len); + return (sp_rt.vt->readlink)(fd, path, len, buf, size, target_len); } sp_err_t sp_sys_get_path_metadata(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_file_meta_t* st) { - return (sp_rt.vt->get_path_metadata)(fd, path, sp_sys_trim_path(path, len), st); + return (sp_rt.vt->get_path_metadata)(fd, path, len, st); } sp_err_t sp_sys_get_link_metadata(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_file_meta_t* st) { - return (sp_rt.vt->get_link_metadata)(fd, path, sp_sys_trim_path(path, len), st); + return (sp_rt.vt->get_link_metadata)(fd, path, len, st); } sp_err_t sp_sys_get_file_metadata(sp_sys_fd_t fd, sp_sys_file_meta_t* st) { @@ -5985,11 +5981,11 @@ sp_err_t sp_sys_get_file_metadata(sp_sys_fd_t fd, sp_sys_file_meta_t* st) { } sp_err_t sp_sys_set_file_perms(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_file_perms_t perms) { - return (sp_rt.vt->set_file_perms)(fd, path, sp_sys_trim_path(path, len), perms); + return (sp_rt.vt->set_file_perms)(fd, path, len, perms); } sp_err_t sp_sys_set_times(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_timespec_t atime, sp_sys_timespec_t mtime) { - return (sp_rt.vt->set_times)(fd, path, sp_sys_trim_path(path, len), atime, mtime); + return (sp_rt.vt->set_times)(fd, path, len, atime, mtime); } sp_err_t sp_sys_clock_gettime(s32 clockid, sp_sys_timespec_t* ts) { @@ -6177,7 +6173,7 @@ s64 sp_sys_lseek(sp_sys_fd_t fd, s64 offset, s32 whence) { } sp_err_t sp_sys_chdir(const c8* path, u32 len) { - return (sp_rt.vt->chdir)(path, sp_sys_trim_path(path, len)); + return (sp_rt.vt->chdir)(path, len); } sp_err_t sp_sys_dir_it_open(sp_sys_fd_t fd, sp_sys_dir_it_t* out) { @@ -6241,6 +6237,10 @@ c8** environ; #define SP_S_ISBLK(m) (((m) & SP_S_IFMT) == SP_S_IFBLK) #define SP_S_ISSOCK(m) (((m) & SP_S_IFMT) == SP_S_IFSOCK) +SP_PRIVATE u32 sp_sys_trim_path(const c8* path, u32 len) { + return sp_fs_trim_path(sp_str(path, len)).len; +} + #if defined(SP_WASM) #define sp_wasi_import(name) \ __attribute__((import_module("wasi_snapshot_preview1"), import_name(name))) @@ -6894,12 +6894,16 @@ SP_PRIVATE sp_err_t sp_sys_err_from_errno(s64 e) { } } -SP_PRIVATE sp_err_t sp_sys_posix_path(const c8* path, u32 len, c8 buf[SP_PATH_MAX]) { +SP_PRIVATE sp_err_t sp_sys_posix_cstr(const c8* str, u32 len, c8 buf[SP_PATH_MAX]) { if (len >= SP_PATH_MAX) return SP_ERR_SYS_NAME_TOO_LONG; - sp_mem_copy(buf, path, len); + sp_mem_copy(buf, str, len); buf[len] = 0; return SP_OK; } + +SP_PRIVATE sp_err_t sp_sys_posix_path(const c8* path, u32 len, c8 buf[SP_PATH_MAX]) { + return sp_sys_posix_cstr(path, sp_sys_trim_path(path, len), buf); +} #endif #if defined(SP_MACOS) || defined(SP_COSMO) @@ -7115,6 +7119,7 @@ typedef struct { SP_PRIVATE sp_nt_status_t sp_sys_nt_target(sp_sys_fd_t root_fd, sp_str_t utf8, u16* result, u32 result_cap, sp_sys_nt_target_t* out) { *out = sp_zero_s(sp_sys_nt_target_t); + utf8 = sp_fs_trim_path(utf8); if (sp_str_empty(utf8)) return SP_NT_STATUS_OBJECT_NAME_NOT_FOUND; if (utf8.len >= SP_PATH_MAX) return SP_NT_STATUS_NAME_TOO_LONG; @@ -8546,6 +8551,7 @@ sp_err_t sp_sys_open_p(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_open_mode return SP_OK; #elif defined(SP_WASM) + len = sp_sys_trim_path(path, len); __wasi_rights_t rights = sp_sys_wasi_open_rights(mode); __wasi_fdflags_t fdflags = (flags & SP_SYS_OPEN_APPEND) ? __WASI_FDFLAGS_APPEND : 0; __wasi_fd_t opened = -1; @@ -8620,6 +8626,7 @@ sp_err_t sp_sys_open_dir_p(sp_sys_fd_t fd, const c8* path, u32 len, u32 flags, s return SP_OK; #elif defined(SP_WASM) + len = sp_sys_trim_path(path, len); __wasi_lookupflags_t lookup = (flags & SP_SYS_OPEN_DIR_NO_FOLLOW) ? 0 : __WASI_LOOKUPFLAGS_SYMLINK_FOLLOW; __wasi_rights_t inheriting = SP_SYS_WASI_DIR_RIGHTS | sp_sys_wasi_open_rights(SP_SYS_OPEN_MODE_RW); __wasi_fd_t opened = -1; @@ -10457,6 +10464,7 @@ sp_err_t sp_sys_get_path_metadata_p(sp_sys_fd_t fd, const c8* path, u32 len, sp_ return SP_OK; #elif defined(SP_WASM) + len = sp_sys_trim_path(path, len); __wasi_filestat_t native = sp_zero; sp_try(sp_sys_err_from_wasi(sp_wasi_path_filestat_get((__wasi_fd_t)fd, __WASI_LOOKUPFLAGS_SYMLINK_FOLLOW, path, len, &native))); sp_sys_file_meta_from_wasi(&native, st); @@ -10495,6 +10503,7 @@ sp_err_t sp_sys_get_link_metadata_p(sp_sys_fd_t fd, const c8* path, u32 len, sp_ return SP_OK; #elif defined(SP_WASM) + len = sp_sys_trim_path(path, len); __wasi_filestat_t native = sp_zero; sp_try(sp_sys_err_from_wasi(sp_wasi_path_filestat_get((__wasi_fd_t)fd, 0, path, len, &native))); sp_sys_file_meta_from_wasi(&native, st); @@ -10628,6 +10637,7 @@ sp_err_t sp_sys_unlink_s(sp_sys_fd_t fd, sp_str_t path) { ////////////////// sp_err_t sp_sys_chdir_p(const c8* path, u32 len) { #if defined(SP_WIN32) + len = sp_sys_trim_path(path, len); if (!len) return SP_ERR_SYS_NOT_FOUND; if (len >= SP_PATH_MAX) return SP_ERR_SYS_NAME_TOO_LONG; SP_ALIGNED u16 wbuf[SP_PATH_MAX + 1]; @@ -10817,7 +10827,7 @@ sp_err_t sp_sys_symlink_p(const c8* existing, u32 existing_len, sp_sys_fd_t to_f c8 existing [SP_PATH_MAX]; c8 alias [SP_PATH_MAX]; } buffers; - sp_try(sp_sys_posix_path(existing, existing_len, buffers.existing)); + sp_try(sp_sys_posix_cstr(existing, existing_len, buffers.existing)); sp_try(sp_sys_posix_path(alias, alias_len, buffers.alias)); return sp_syscall_e(SP_SYSCALL_NUM_SYMLINKAT, buffers.existing, to_fd, buffers.alias); @@ -10826,7 +10836,7 @@ sp_err_t sp_sys_symlink_p(const c8* existing, u32 existing_len, sp_sys_fd_t to_f c8 existing [SP_PATH_MAX]; c8 alias [SP_PATH_MAX]; } buffers; - sp_try(sp_sys_posix_path(existing, existing_len, buffers.existing)); + sp_try(sp_sys_posix_cstr(existing, existing_len, buffers.existing)); sp_try(sp_sys_posix_path(alias, alias_len, buffers.alias)); return sp_sys_err_from_libc(symlinkat(buffers.existing, to_fd, buffers.alias)); From 34b2148d13162be2896fbaca42b9219c8de6b0b5 Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Sat, 26 Sep 2026 16:40:43 -0400 Subject: [PATCH 32/72] sys: prestat roots, fd_path on the cwd; fs: prefix matching in path_at_root, sp_rt.cwd --- sp.h | 228 +++++++++++++++++++++++-------------------- sp/sp_cli.h | 4 +- test/cli/complete.c | 7 +- test/fs/at_root.c | 165 +++++++++++++++++++++++++++++++ test/fs/get_cwd.c | 43 ++++---- test/sys/path.c | 28 +++--- test/sys/root.c | 35 +++++-- test/sys/self_path.c | 17 +++- 8 files changed, 367 insertions(+), 160 deletions(-) create mode 100644 test/fs/at_root.c diff --git a/sp.h b/sp.h index 47a129df..6baf0840 100644 --- a/sp.h +++ b/sp.h @@ -942,6 +942,7 @@ typedef OVERLAPPED sp_win32_overlapped_t; #define SP_EROFS 30 #define SP_EMLINK 31 #define SP_EPIPE 32 + #define SP_ERANGE 34 #define SP_ENAMETOOLONG 36 #define SP_ENOSYS 38 #define SP_ENOTEMPTY 39 @@ -1080,6 +1081,7 @@ typedef OVERLAPPED sp_win32_overlapped_t; #define SP_EROFS EROFS #define SP_EMLINK EMLINK #define SP_EPIPE EPIPE + #define SP_ERANGE ERANGE #define SP_ENAMETOOLONG ENAMETOOLONG #define SP_ENOSYS ENOSYS #define SP_ENOTEMPTY ENOTEMPTY @@ -1454,12 +1456,11 @@ SP_API sp_err_t sp_sys_pwrite(sp_sys_fd_t fd, const void* buf, u64 count, u64 SP_API sp_err_t sp_sys_transfer(sp_sys_fd_t in, u64* in_pos, sp_sys_fd_t out, u64 count, u64* bytes_moved); SP_API sp_err_t sp_sys_transfer_positional(sp_sys_fd_t in, u64* in_pos, sp_sys_fd_t out, u64 count, u64 offset, u64* bytes_moved); SP_API sp_sys_fd_t sp_sys_get_root(s32 it); -SP_API s64 sp_sys_get_root_name(s32 it, c8* buf, u64 size); +SP_API sp_err_t sp_sys_get_root_name(s32 it, c8* buf, u64 size, u64* len); SP_API s64 sp_sys_get_exe_path(c8* buf, u64 size); -SP_API s64 sp_sys_get_cwd_path(c8* buf, u64 size); SP_API s64 sp_sys_get_storage_path(c8* buf, u64 size); SP_API s64 sp_sys_get_config_path(c8* buf, u64 size); -SP_API s64 sp_sys_get_fd_path(sp_sys_fd_t fd, c8* buf, u64 size); +SP_API sp_err_t sp_sys_get_fd_path(sp_sys_fd_t fd, c8* buf, u64 size, u64* len); SP_API sp_err_t sp_sys_open(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_open_mode_t mode, u32 flags, sp_sys_fd_t* out); SP_API sp_err_t sp_sys_open_dir(sp_sys_fd_t fd, const c8* path, u32 len, u32 flags, sp_sys_fd_t* out); SP_API sp_err_t sp_sys_close(sp_sys_fd_t fd); @@ -1554,12 +1555,11 @@ typedef struct { sp_err_t (*transfer)(sp_sys_fd_t in, u64* in_pos, sp_sys_fd_t out, u64 count, u64* bytes_moved); sp_err_t (*transfer_positional)(sp_sys_fd_t in, u64* in_pos, sp_sys_fd_t out, u64 count, u64 offset, u64* bytes_moved); sp_sys_fd_t (*get_root)(s32 it); - s64 (*get_root_name)(s32 it, c8* buf, u64 size); + sp_err_t (*get_root_name)(s32 it, c8* buf, u64 size, u64* len); s64 (*get_exe_path)(c8* buf, u64 size); - s64 (*get_cwd_path)(c8* buf, u64 size); s64 (*get_storage_path)(c8* buf, u64 size); s64 (*get_config_path)(c8* buf, u64 size); - s64 (*get_fd_path)(sp_sys_fd_t fd, c8* buf, u64 size); + sp_err_t (*get_fd_path)(sp_sys_fd_t fd, c8* buf, u64 size, u64* len); sp_err_t (*open)(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_open_mode_t mode, u32 flags, sp_sys_fd_t* out); sp_err_t (*open_dir)(sp_sys_fd_t fd, const c8* path, u32 len, u32 flags, sp_sys_fd_t*); sp_err_t (*close)(sp_sys_fd_t fd); @@ -1630,12 +1630,11 @@ SP_API sp_err_t sp_sys_pwrite_p(sp_sys_fd_t fd, const void* buf, u64 count, u SP_API sp_err_t sp_sys_transfer_p(sp_sys_fd_t in, u64* in_pos, sp_sys_fd_t out, u64 count, u64* bytes_moved); SP_API sp_err_t sp_sys_transfer_positional_p(sp_sys_fd_t in, u64* in_pos, sp_sys_fd_t out, u64 count, u64 offset, u64* bytes_moved); SP_API sp_sys_fd_t sp_sys_get_root_p(s32 it); -SP_API s64 sp_sys_get_root_name_p(s32 it, c8* buf, u64 size); +SP_API sp_err_t sp_sys_get_root_name_p(s32 it, c8* buf, u64 size, u64* len); SP_API s64 sp_sys_get_exe_path_p(c8* buf, u64 size); -SP_API s64 sp_sys_get_cwd_path_p(c8* buf, u64 size); SP_API s64 sp_sys_get_storage_path_p(c8* buf, u64 size); SP_API s64 sp_sys_get_config_path_p(c8* buf, u64 size); -SP_API s64 sp_sys_get_fd_path_p(sp_sys_fd_t fd, c8* buf, u64 size); +SP_API sp_err_t sp_sys_get_fd_path_p(sp_sys_fd_t fd, c8* buf, u64 size, u64* len); SP_API sp_err_t sp_sys_open_p(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_open_mode_t mode, u32 flags, sp_sys_fd_t* out); SP_API sp_err_t sp_sys_open_dir_p(sp_sys_fd_t fd, const c8* path, u32 len, u32 flags, sp_sys_fd_t* out); SP_API sp_err_t sp_sys_close_p(sp_sys_fd_t fd); @@ -3113,7 +3112,6 @@ SP_API sp_err_t sp_fs_copy_at(sp_path_t from, sp_path_t to, sp_fs_at SP_API sp_err_t sp_fs_copy_file_at(sp_path_t from, sp_path_t to, sp_fs_atomic_mode_t mode); SP_API sp_err_t sp_fs_copy_tree_at(sp_path_t from, sp_path_t to, sp_fs_atomic_mode_t mode); SP_API sp_sys_fd_t sp_fs_get_cwd(void); -SP_API sp_sys_fd_t sp_fs_get_root(sp_str_t name); SP_API sp_path_t sp_path_at_root(sp_str_t sub); // literally sys+normalize, if normalize isnt needed this isn't either? @@ -4651,6 +4649,7 @@ typedef struct { typedef struct { const sp_sys_vtable_t* vt; + sp_sys_fd_t cwd; sp_os_signal_handler_t signal_handlers[3]; void* signal_userdata[3]; sp_spin_lock_t locks [SP_RT_NUM_SPIN_LOCKS]; @@ -5723,7 +5722,6 @@ const sp_sys_vtable_t sp_sys_vtable_platform = { .get_root = sp_sys_get_root_p, .get_root_name = sp_sys_get_root_name_p, .get_exe_path = sp_sys_get_exe_path_p, - .get_cwd_path = sp_sys_get_cwd_path_p, .get_storage_path = sp_sys_get_storage_path_p, .get_config_path = sp_sys_get_config_path_p, .get_fd_path = sp_sys_get_fd_path_p, @@ -5792,6 +5790,7 @@ const sp_sys_vtable_t sp_sys_vtable_platform = { sp_rt_t sp_rt = { .vt = &sp_sys_vtable_platform, + .cwd = SP_SYS_INVALID_FD, .err_str = sp_err_str, }; @@ -5896,18 +5895,14 @@ sp_sys_fd_t sp_sys_get_root(s32 it) { return (sp_rt.vt->get_root)(it); } -s64 sp_sys_get_root_name(s32 it, c8* buf, u64 size) { - return (sp_rt.vt->get_root_name)(it, buf, size); +sp_err_t sp_sys_get_root_name(s32 it, c8* buf, u64 size, u64* len) { + return (sp_rt.vt->get_root_name)(it, buf, size, len); } s64 sp_sys_get_exe_path(c8* buf, u64 size) { return (sp_rt.vt->get_exe_path)(buf, size); } -s64 sp_sys_get_cwd_path(c8* buf, u64 size) { - return (sp_rt.vt->get_cwd_path)(buf, size); -} - s64 sp_sys_get_storage_path(c8* buf, u64 size) { return (sp_rt.vt->get_storage_path)(buf, size); } @@ -5916,8 +5911,8 @@ s64 sp_sys_get_config_path(c8* buf, u64 size) { return (sp_rt.vt->get_config_path)(buf, size); } -s64 sp_sys_get_fd_path(sp_sys_fd_t fd, c8* buf, u64 size) { - return (sp_rt.vt->get_fd_path)(fd, buf, size); +sp_err_t sp_sys_get_fd_path(sp_sys_fd_t fd, c8* buf, u64 size, u64* len) { + return (sp_rt.vt->get_fd_path)(fd, buf, size, len); } sp_err_t sp_sys_open(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_open_mode_t mode, u32 flags, sp_sys_fd_t* out) { @@ -6341,6 +6336,21 @@ SP_PRIVATE u32 sp_sys_trim_path(const c8* path, u32 len) { __wasi_timestamp_t ctim; } __wasi_filestat_t; + typedef uint8_t __wasi_preopentype_t; + + typedef struct __wasi_prestat_dir_t { + __wasi_size_t pr_name_len; + } __wasi_prestat_dir_t; + + typedef union __wasi_prestat_u_t { + __wasi_prestat_dir_t dir; + } __wasi_prestat_u_t; + + typedef struct __wasi_prestat_t { + __wasi_preopentype_t tag; + __wasi_prestat_u_t u; + } __wasi_prestat_t; + #define __WASI_EVENTTYPE_CLOCK ((__wasi_eventtype_t)0) #define __WASI_EVENTTYPE_FD_READ ((__wasi_eventtype_t)1) #define __WASI_EVENTTYPE_FD_WRITE ((__wasi_eventtype_t)2) @@ -6348,6 +6358,7 @@ SP_PRIVATE u32 sp_sys_trim_path(const c8* path, u32 len) { #define __WASI_WHENCE_CUR ((__wasi_whence_t)1) #define __WASI_WHENCE_END ((__wasi_whence_t)2) #define __WASI_DIRCOOKIE_START ((__wasi_dircookie_t)0) + #define __WASI_PREOPENTYPE_DIR ((__wasi_preopentype_t)0) #define __WASI_FILETYPE_UNKNOWN ((__wasi_filetype_t)0) #define __WASI_FILETYPE_BLOCK_DEVICE ((__wasi_filetype_t)1) #define __WASI_FILETYPE_CHARACTER_DEVICE ((__wasi_filetype_t)2) @@ -6450,6 +6461,12 @@ SP_PRIVATE u32 sp_sys_trim_path(const c8* path, u32 len) { sp_wasi_import("fd_filestat_get") extern __wasi_errno_t sp_wasi_fd_filestat_get(__wasi_fd_t fd, __wasi_filestat_t* filestat); + sp_wasi_import("fd_prestat_get") + extern __wasi_errno_t sp_wasi_fd_prestat_get(__wasi_fd_t fd, __wasi_prestat_t* prestat); + + sp_wasi_import("fd_prestat_dir_name") + extern __wasi_errno_t sp_wasi_fd_prestat_dir_name(__wasi_fd_t fd, u8* path, __wasi_size_t path_len); + sp_wasi_import("path_create_directory") extern __wasi_errno_t sp_wasi_path_create_directory(__wasi_fd_t fd, const c8* path, __wasi_size_t path_len); @@ -6549,6 +6566,16 @@ SP_PRIVATE sp_err_t sp_sys_err_from_wasi(__wasi_errno_t e) { default: return SP_ERR_SYS; } } + +SP_PRIVATE sp_err_t sp_sys_wasi_prestat_name(__wasi_fd_t fd, c8* buf, u64 size, u64* len) { + __wasi_prestat_t prestat; + sp_try(sp_sys_err_from_wasi(sp_wasi_fd_prestat_get(fd, &prestat))); + if (prestat.u.dir.pr_name_len >= size) return SP_ERR_SYS_NAME_TOO_LONG; + sp_try(sp_sys_err_from_wasi(sp_wasi_fd_prestat_dir_name(fd, (u8*)buf, prestat.u.dir.pr_name_len))); + buf[prestat.u.dir.pr_name_len] = 0; + *len = prestat.u.dir.pr_name_len; + return SP_OK; +} #endif @@ -8665,6 +8692,8 @@ sp_sys_fd_t sp_sys_get_root_p(s32 it) { return (sp_sys_fd_t)SP_AT_FDCWD; #elif defined(SP_WASM) + __wasi_prestat_t prestat; + if (sp_wasi_fd_prestat_get((__wasi_fd_t)(3 + it), &prestat)) return SP_SYS_INVALID_FD; return (sp_sys_fd_t)(3 + it); #else @@ -8672,16 +8701,16 @@ sp_sys_fd_t sp_sys_get_root_p(s32 it) { #endif } -s64 sp_sys_get_root_name_p(s32 it, c8* buf, u64 size) { +sp_err_t sp_sys_get_root_name_p(s32 it, c8* buf, u64 size, u64* len) { + *len = 0; + #if defined(SP_WIN32) || defined(SP_LINUX) || defined(SP_MACOS) || defined(SP_COSMO) - if (it != 0 || !buf || size < 2) return -1; - buf[0] = '.'; - buf[1] = 0; - return 1; + (void)buf; (void)size; + if (it != 0) return SP_ERR_SYS_BAD_FD; + return SP_ERR_SYS_UNSUPPORTED; #elif defined(SP_WASM) - (void)it; (void)buf; (void)size; - return -1; + return sp_sys_wasi_prestat_name((__wasi_fd_t)(3 + it), buf, size, len); #else #error "sp_sys_get_root_name" @@ -11033,46 +11062,64 @@ sp_err_t sp_sys_set_times_s(sp_sys_fd_t fd, sp_str_t path, sp_sys_timespec_t ati //////////////////////// // SP_SYS_GET_FD_PATH // //////////////////////// -s64 sp_sys_get_fd_path_p(sp_sys_fd_t fd, c8* buf, u64 size) { -#if defined(SP_WIN32) - if (!buf) return -1; +sp_err_t sp_sys_get_fd_path_p(sp_sys_fd_t fd, c8* buf, u64 size, u64* len) { + *len = 0; +#if defined(SP_WIN32) u16 wbuf[SP_PATH_MAX]; DWORD wlen = GetFinalPathNameByHandleW((HANDLE)fd, (LPWSTR)wbuf, SP_PATH_MAX, 0); - if (wlen == 0 || wlen >= SP_PATH_MAX) return -1; + if (wlen == 0) return sp_sys_err_from_win32(GetLastError()); + if (wlen >= SP_PATH_MAX) return SP_ERR_SYS_NAME_TOO_LONG; sp_wide_str_t wide = sp_sys_dos_from_nt_wtf16(wbuf, wlen); SP_ALIGNED c8 u8buf[SP_PATH_MAX * 3 + 1]; sp_mem_fixed_t fixed = sp_mem_fixed(u8buf, sizeof(u8buf)); sp_str_t utf8; - if (sp_wtf16_to_wtf8(sp_mem_fixed_as_allocator(&fixed), wide, &utf8) != SP_OK) return -1; - return sp_sys_win32_copy_path(utf8, buf, size); + sp_try(sp_wtf16_to_wtf8(sp_mem_fixed_as_allocator(&fixed), wide, &utf8)); + s64 n = sp_sys_win32_copy_path(utf8, buf, size); + if (n < 0) return SP_ERR_SYS_NAME_TOO_LONG; + *len = (u64)n; + return SP_OK; #elif defined(SP_LINUX) + if (fd == SP_AT_FDCWD) { + s64 rc = sp_syscall(SP_SYSCALL_NUM_GETCWD, buf, size); + if (rc == -SP_ERANGE) return SP_ERR_SYS_NAME_TOO_LONG; + if (sp_sys_is_err(rc)) return sp_sys_err_from_errno(-rc); + *len = (u64)rc - 1; + return SP_OK; + } + c8 proc [64] = sp_zero; sp_fmt_buf(proc, 64, "/proc/self/fd/{}", sp_fmt_int(fd)); c8 rbuf [SP_PATH_MAX]; - s64 n = sp_syscall(SP_SYSCALL_NUM_READLINKAT, SP_AT_FDCWD, proc, rbuf, SP_PATH_MAX); - if (n < 0 || n == SP_PATH_MAX || (u64)n >= size) return -1; - sp_mem_copy(buf, rbuf, (u64)n); - buf[n] = 0; - return n; + s64 rc = sp_syscall(SP_SYSCALL_NUM_READLINKAT, SP_AT_FDCWD, proc, rbuf, SP_PATH_MAX); + if (sp_sys_is_err(rc)) return sp_sys_err_from_errno(-rc); + if (rc == SP_PATH_MAX || (u64)rc >= size) return SP_ERR_SYS_NAME_TOO_LONG; + sp_mem_copy(buf, rbuf, (u64)rc); + buf[rc] = 0; + *len = (u64)rc; + return SP_OK; #elif defined(SP_MACOS) || defined(SP_COSMO) - if (!buf || size == 0) return -1; - c8 resolved [SP_PATH_MAX]; - if (fcntl((s32)fd, F_GETPATH, resolved) < 0) return -1; + if (fd == SP_AT_FDCWD) { + if (!getcwd(resolved, sizeof(resolved))) return sp_sys_err_from_errno(errno); + } + else if (fcntl((s32)fd, F_GETPATH, resolved) < 0) { + return sp_sys_err_from_errno(errno); + } + u64 n = sp_cstr_len(resolved); - if (n >= size) return -1; + if (n >= size) return SP_ERR_SYS_NAME_TOO_LONG; sp_mem_copy(buf, resolved, n); buf[n] = 0; - return (s64)n; + *len = n; + return SP_OK; #elif defined(SP_WASM) - (void)fd; (void)buf; (void)size; - return -1; + return sp_sys_wasi_prestat_name((__wasi_fd_t)fd, buf, size, len); #else #error "sp_sys_get_fd_path" @@ -11137,51 +11184,6 @@ s64 sp_sys_get_exe_path_p(c8* buf, u64 size) { #endif } -///////////////////////// -// SP_SYS_GET_CWD_PATH // -///////////////////////// -s64 sp_sys_get_cwd_path_p(c8* buf, u64 size) { -#if defined(SP_WIN32) - if (!buf) return -1; - - sp_nt_unicode_string_t* cwd = (sp_nt_unicode_string_t*)(sp_nt_process_params() + 0x38); - u32 wlen = cwd->Length / (u32)sizeof(u16); - SP_ALIGNED c8 u8buf[SP_PATH_MAX * 3 + 1]; - sp_mem_fixed_t fixed = sp_mem_fixed(u8buf, sizeof(u8buf)); - sp_str_t utf8; - if (sp_wtf16_to_wtf8(sp_mem_fixed_as_allocator(&fixed), (sp_wide_str_t) { .data = cwd->Buffer, .len = wlen }, &utf8) != SP_OK) return -1; - - // The PEB DosPath always carries a trailing separator; strip it the way - // GetCurrentDirectoryW does, except at a drive root, where "C:" would mean - // something else (drive-relative). - c8 last = utf8.len ? utf8.data[utf8.len - 1] : 0; - if (utf8.len > 1 && (last == '\\' || last == '/') && !(utf8.len == 3 && utf8.data[1] == ':')) { - utf8.len--; - } - - return sp_sys_win32_copy_path(utf8, buf, size); - -#elif defined(SP_LINUX) - s64 n = sp_syscall(SP_SYSCALL_NUM_GETCWD, buf, size); - if (n <= 0) return -1; - return n - 1; - -#elif defined(SP_MACOS) || defined(SP_COSMO) - if (!buf || size == 0) return -1; - if (!getcwd(buf, size)) return -1; - return (s64)sp_cstr_len(buf); - -#elif defined(SP_WASM) - if (!buf || size < 2) return -1; - buf[0] = '.'; - buf[1] = 0; - return 1; - -#else - #error "sp_sys_get_cwd_path" -#endif -} - ///////////////////// // SP_OS_GET_NAME // ///////////////////// @@ -20116,25 +20118,34 @@ sp_str_t sp_fs_replace_ext(sp_mem_t mem, sp_str_t path, sp_str_t ext) { sp_str_join(mem, stripped, ext, sp_str_lit(".")); } -sp_sys_fd_t sp_fs_get_root(sp_str_t name) { - name = sp_fs_trim_path(name); +sp_sys_fd_t sp_fs_get_cwd(void) { + if (sp_rt.cwd != SP_SYS_INVALID_FD) return sp_rt.cwd; + return sp_sys_get_root(0); +} + +sp_path_t sp_path_at_root(sp_str_t sub) { + sp_path_t best = { .dir = sp_fs_get_cwd(), .sub = sub }; + if (!sp_fs_is_absolute(sub)) return best; + + u32 longest = 0; for (s32 it = 0;; it++) { sp_sys_fd_t fd = sp_sys_get_root(it); - if (fd == SP_SYS_INVALID_FD) return SP_SYS_INVALID_FD; + if (fd == SP_SYS_INVALID_FD) return best; c8 buf [SP_PATH_MAX]; - s64 len = sp_sys_get_root_name(it, buf, SP_PATH_MAX); - if (len < 0) return SP_SYS_INVALID_FD; - if (sp_str_equal(sp_fs_trim_path(sp_str(buf, len)), name)) return fd; - } -} + u64 len = 0; + if (sp_sys_get_root_name(it, buf, SP_PATH_MAX, &len)) continue; -sp_sys_fd_t sp_fs_get_cwd(void) { - return sp_fs_get_root(sp_str_lit(".")); -} + sp_str_t name = sp_fs_trim_path(sp_str(buf, (u32)len)); + if (name.len <= longest) continue; + if (!sp_str_starts_with(sub, name)) continue; + if (name.len < sub.len && !sp_fs_is_sep(sub.data[name.len]) && !sp_fs_is_sep(sp_str_back(name))) continue; -sp_path_t sp_path_at_root(sp_str_t sub) { - return (sp_path_t) { .dir = sp_fs_get_cwd(), .sub = sub }; + sp_str_t rest = sp_str_suffix(sub, (s32)(sub.len - name.len)); + while (!sp_str_empty(rest) && sp_fs_is_sep(rest.data[0])) rest = sp_str_suffix(rest, (s32)rest.len - 1); + longest = name.len; + best = (sp_path_t) { .dir = fd, .sub = sp_str_empty(rest) ? sp_str_lit(".") : rest }; + } } sp_fs_kind_t sp_fs_get_link_kind_at(sp_path_t path) { @@ -20235,11 +20246,12 @@ SP_PRIVATE sp_str_t sp_fs_canonicalize_to(sp_path_t path, c8 buf [SP_PATH_MAX]) sp_sys_fd_t fd = SP_SYS_INVALID_FD; if (sp_sys_open_s(path.dir, path.sub, SP_SYS_OPEN_MODE_PATH, 0, &fd)) return sp_zero_s(sp_str_t); - s64 len = sp_sys_get_fd_path(fd, buf, SP_PATH_MAX); + u64 len = 0; + sp_err_t err = sp_sys_get_fd_path(fd, buf, SP_PATH_MAX, &len); sp_sys_close(fd); - if (len <= 0) return sp_zero_s(sp_str_t); + if (err) return sp_zero_s(sp_str_t); - return sp_str(buf, len); + return sp_str(buf, (u32)len); } sp_str_t sp_fs_canonicalize_path_at(sp_mem_t mem, sp_path_t path) { @@ -20260,9 +20272,9 @@ sp_str_t sp_fs_get_exe_path(sp_mem_t mem) { sp_str_t sp_fs_get_cwd_path(sp_mem_t mem) { c8 buf[SP_PATH_MAX]; - s64 len = sp_sys_get_cwd_path(buf, SP_PATH_MAX); - if (len <= 0) return sp_zero_s(sp_str_t); - return sp_str_copy(mem, sp_str(buf, len)); + u64 len = 0; + if (sp_sys_get_fd_path(sp_fs_get_cwd(), buf, SP_PATH_MAX, &len)) return sp_zero_s(sp_str_t); + return sp_str_copy(mem, sp_str(buf, (u32)len)); } sp_str_t sp_fs_get_storage_path(sp_mem_t mem) { diff --git a/sp/sp_cli.h b/sp/sp_cli.h index 1396eb10..1ff43287 100644 --- a/sp/sp_cli.h +++ b/sp/sp_cli.h @@ -1381,8 +1381,8 @@ SP_PRIVATE sp_str_t sp_cli_completer_path(sp_cli_desc_t desc, c8* buffer, u32 le if (sp_fs_is_absolute(arg0)) return arg0; c8 cwd [SP_PATH_MAX]; - s64 cwd_len = sp_sys_get_cwd_path(cwd, sizeof(cwd)); - if (cwd_len <= 0) return arg0; + u64 cwd_len = 0; + if (sp_sys_get_fd_path(sp_fs_get_cwd(), cwd, sizeof(cwd), &cwd_len)) return arg0; if (sp_cast(u32, cwd_len) + 1 + arg0.len > len) return arg0; sp_io_mem_writer_t path = sp_zero; diff --git a/test/cli/complete.c b/test/cli/complete.c index 69ad501e..960b3d7d 100644 --- a/test/cli/complete.c +++ b/test/cli/complete.c @@ -409,9 +409,10 @@ static void run_cli_completer_path_test(s32* utest_result, sp_mem_t mem, cli_com if (t.expect.anchored) { c8 cwd [SP_PATH_MAX]; - s64 cwd_len = sp_sys_get_cwd_path(cwd, sizeof(cwd)); - EXPECT_GT(cwd_len, 0); - if (cwd_len <= 0) return; + u64 cwd_len = 0; + sp_err_t err = sp_sys_get_fd_path(sp_fs_get_cwd(), cwd, sizeof(cwd), &cwd_len); + EXPECT_EQ(err, SP_OK); + if (err) return; sp_str_t expected = sp_fmt(mem, "{}/{}", sp_fmt_str(sp_str(cwd, sp_cast(u32, cwd_len))), sp_fmt_cstr(t.arg0)).value; SP_EXPECT_STR_EQ(path, expected); } diff --git a/test/fs/at_root.c b/test/fs/at_root.c new file mode 100644 index 00000000..55110086 --- /dev/null +++ b/test/fs/at_root.c @@ -0,0 +1,165 @@ +#include "sp.h" +#include "sp/sp_test.h" + +#define AT_ROOT_MAX_ROOTS 4 +#define AT_ROOT_FD_BASE 9000 + +typedef struct { + const c8* name; + sp_err_t err; +} at_root_root_t; + +typedef struct { + s32 root; + bool invalid; + const c8* sub; +} at_root_expect_t; + +typedef struct { + const c8* name; + at_root_root_t roots [AT_ROOT_MAX_ROOTS]; + const c8* sub; + at_root_expect_t expect; +} at_root_test_t; + +static const at_root_test_t* active; + +static s32 num_roots(void) { + s32 n = 0; + sp_carr_for(active->roots, it) { + if (!active->roots[it].name && !active->roots[it].err) break; + n++; + } + return n; +} + +static sp_sys_fd_t fake_get_root(s32 it) { + if (it < 0 || it >= num_roots()) return SP_SYS_INVALID_FD; + return (sp_sys_fd_t)(AT_ROOT_FD_BASE + it); +} + +static sp_err_t fake_get_root_name(s32 it, c8* buf, u64 size, u64* len) { + *len = 0; + if (it < 0 || it >= num_roots()) return SP_ERR_SYS_BAD_FD; + + const at_root_root_t* root = &active->roots[it]; + if (root->err) return root->err; + + u64 n = sp_cstr_len(root->name); + if (n >= size) return SP_ERR_SYS_NAME_TOO_LONG; + sp_mem_copy(buf, root->name, n); + buf[n] = 0; + *len = n; + return SP_OK; +} + +static const at_root_test_t tests [] = { + { + .name = "unlabeled_root_takes_relative", + .roots = { { .err = SP_ERR_SYS_UNSUPPORTED } }, + .sub = "x/y", + .expect = { .sub = "x/y" }, + }, + { + .name = "unlabeled_root_takes_absolute", + .roots = { { .err = SP_ERR_SYS_UNSUPPORTED } }, + .sub = "/etc/x", + .expect = { .sub = "/etc/x" }, + }, + { + .name = "relative_ignores_labels", + .roots = { { .name = "." }, { .name = "/cfg" } }, + .sub = "cfg/a", + .expect = { .sub = "cfg/a" }, + }, + { + .name = "label_prefix_is_stripped", + .roots = { { .name = "/bar" } }, + .sub = "/bar/x", + .expect = { .sub = "x" }, + }, + { + .name = "exact_label_is_dot", + .roots = { { .name = "/bar" } }, + .sub = "/bar", + .expect = { .sub = "." }, + }, + { + .name = "trailing_separator_is_dot", + .roots = { { .name = "/bar" } }, + .sub = "/bar/", + .expect = { .sub = "." }, + }, + { + .name = "label_matches_whole_components", + .roots = { { .name = "/bar" } }, + .sub = "/barn/x", + .expect = { .sub = "/barn/x" }, + }, + { + .name = "longest_label_wins", + .roots = { { .name = "/bar" }, { .name = "/bar/x" } }, + .sub = "/bar/x/y", + .expect = { .root = 1, .sub = "y" }, + }, + { + .name = "longest_label_wins_regardless_of_order", + .roots = { { .name = "/bar/x" }, { .name = "/bar" } }, + .sub = "/bar/x/y", + .expect = { .sub = "y" }, + }, + { + .name = "slash_label_takes_everything", + .roots = { { .name = "/" } }, + .sub = "/x/y", + .expect = { .sub = "x/y" }, + }, + { + .name = "label_trailing_separator_is_ignored", + .roots = { { .name = "/bar/" } }, + .sub = "/bar/x", + .expect = { .sub = "x" }, + }, + { + .name = "dot_label_does_not_match_absolute", + .roots = { { .name = "." }, { .name = "/cfg" } }, + .sub = "/cfg/a", + .expect = { .root = 1, .sub = "a" }, + }, + { + .name = "unmatched_absolute_falls_back_to_cwd", + .roots = { { .name = "." }, { .name = "/cfg" } }, + .sub = "/other", + .expect = { .sub = "/other" }, + }, + { + .name = "unlabeled_root_never_wins", + .roots = { { .err = SP_ERR_SYS_UNSUPPORTED }, { .name = "/bar" } }, + .sub = "/bar/x", + .expect = { .root = 1, .sub = "x" }, + }, + { + .name = "no_roots_is_invalid", + .sub = "x", + .expect = { .invalid = true, .sub = "x" }, + }, +}; + +static sp_err_t run(sp_test_t* t, at_root_test_t* c) { + sp_sys_vtable_t vt = sp_sys_vtable_platform; + vt.get_root = fake_get_root; + vt.get_root_name = fake_get_root_name; + + active = c; + const sp_sys_vtable_t* saved = sp_sys_set_vtable(&vt); + sp_path_t path = sp_path_at_root(sp_cstr_as_str(c->sub)); + sp_sys_set_vtable(saved); + active = SP_NULLPTR; + + sp_sys_fd_t dir = c->expect.invalid ? SP_SYS_INVALID_FD : (sp_sys_fd_t)(AT_ROOT_FD_BASE + c->expect.root); + sp_expect_eq(t, path.dir, dir); + sp_expect_str_eq(t, path.sub, sp_cstr_as_str(c->expect.sub)); + return SP_OK; +} + +sp_test_each_fn(fs, at_root, at_root_test_t, tests, run, .serial = true); diff --git a/test/fs/get_cwd.c b/test/fs/get_cwd.c index 60523cbf..d459378a 100644 --- a/test/fs/get_cwd.c +++ b/test/fs/get_cwd.c @@ -33,36 +33,31 @@ sp_test(fs, get_cwd_unlinked_cwd_does_not_leak_deleted_suffix, .serial = true) { #endif } -typedef struct { - const c8* name; - bool found; -} fs_get_root_t; - -static sp_err_t run_get_root(sp_test_t* t, fs_get_root_t c) { - sp_sys_fd_t fd = sp_fs_get_root(sp_cstr_as_str(c.name)); - sp_expect_eq(t, fd != SP_SYS_INVALID_FD, c.found); - if (c.found) sp_expect_eq(t, fd, sp_fs_get_cwd()); +sp_test(fs, get_cwd_is_root_zero) { + sp_test_skip_on_wasm() + sp_expect_eq(t, sp_fs_get_cwd(), sp_sys_get_root(0)); return SP_OK; } -sp_test(fs, get_root_by_name) { +sp_test(fs, get_cwd_override, .serial = true) { sp_test_skip_on_wasm() - fs_get_root_t cases [] = { - { .name = ".", .found = true }, - { .name = "./", .found = true }, - { .name = "" }, - { .name = ".." }, - { .name = "/" }, - { .name = "/config" }, - }; - sp_carr_for(cases, it) sp_try(run_get_root(t, cases[it])); - return SP_OK; -} + sp_sys_fd_t dir = SP_SYS_INVALID_FD; + sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sp_test_dir(t), 0, &dir)); + sp_must_ok(t, sp_fs_create_file_at((sp_path_t) { .dir = dir, .sub = sp_str_lit("A") })); -sp_test(fs, get_cwd_is_root_zero) { - sp_test_skip_on_wasm() - sp_expect_eq(t, sp_fs_get_cwd(), sp_sys_get_root(0)); + sp_rt.cwd = dir; + sp_sys_fd_t cwd = sp_fs_get_cwd(); + sp_path_t path = sp_path_at_root(sp_str_lit("A")); + bool found = sp_fs_is_file(sp_str_lit("A")); + sp_str_t cwd_path = sp_fs_get_cwd_path(sp_test_arena(t)); + sp_rt.cwd = SP_SYS_INVALID_FD; + sp_sys_close(dir); + + sp_expect_eq(t, cwd, dir); + sp_expect_eq(t, path.dir, dir); + sp_expect(t, found); + sp_expect_str_eq(t, cwd_path, sp_fs_canonicalize_path(sp_test_arena(t), sp_test_dir(t))); return SP_OK; } diff --git a/test/sys/path.c b/test/sys/path.c index 5d7fe42c..b1e22030 100644 --- a/test/sys/path.c +++ b/test/sys/path.c @@ -408,8 +408,8 @@ sp_test(sys, get_fd_path_refuses_overflow) { sp_must_ok(t, sp_sys_open_s(sp_sys_get_root(0), path, SP_SYS_OPEN_MODE_PATH, 0, &fd)); c8 full [SP_PATH_MAX] = sp_zero; - s64 len = sp_sys_get_fd_path(fd, full, sizeof(full)); - if (len <= 0) { + u64 len = 0; + if (sp_sys_get_fd_path(fd, full, sizeof(full), &len)) { sp_sys_close(fd); sp_test_fail(t, "get_fd_path failed with a full-size buffer"); return SP_OK; @@ -417,27 +417,31 @@ sp_test(sys, get_fd_path_refuses_overflow) { c8 buf [SP_PATH_MAX]; sp_for(it, sizeof(buf)) buf[it] = (c8)0xAB; - s64 n = sp_sys_get_fd_path(fd, buf, (u64)len + 1); - if (n != len) { - sp_test_fail(t, "exact fit returned {} but expected {}", sp_fmt_int(n), sp_fmt_int(len)); + u64 n = 0; + sp_err_t err = sp_sys_get_fd_path(fd, buf, len + 1, &n); + if (err) { + sp_test_fail(t, "exact fit failed: {}", sp_fmt_str(sp_err_str(err))); + } + else if (n != len) { + sp_test_fail(t, "exact fit returned {} but expected {}", sp_fmt_int((s64)n), sp_fmt_int((s64)len)); } else if (buf[len] != 0) { sp_test_fail(t, "exact fit did not NUL-terminate"); } - else if (!sp_mem_is_equal(buf, full, (u64)len)) { + else if (!sp_mem_is_equal(buf, full, len)) { sp_test_fail(t, "exact fit returned different path"); } sp_for(it, sizeof(buf)) buf[it] = (c8)0xAB; - n = sp_sys_get_fd_path(fd, buf, (u64)len); - if (n != -1) { - sp_test_fail(t, "no room for NUL: returned {} but expected -1", sp_fmt_int(n)); + err = sp_sys_get_fd_path(fd, buf, len, &n); + if (err != SP_ERR_SYS_NAME_TOO_LONG) { + sp_test_fail(t, "no room for NUL: returned {} but expected SP_ERR_SYS_NAME_TOO_LONG", sp_fmt_str(sp_err_str(err))); } sp_for(it, sizeof(buf)) buf[it] = (c8)0xAB; - n = sp_sys_get_fd_path(fd, buf, (u64)len - 1); - if (n != -1) { - sp_test_fail(t, "undersized buffer: returned {} but expected -1", sp_fmt_int(n)); + err = sp_sys_get_fd_path(fd, buf, len - 1, &n); + if (err != SP_ERR_SYS_NAME_TOO_LONG) { + sp_test_fail(t, "undersized buffer: returned {} but expected SP_ERR_SYS_NAME_TOO_LONG", sp_fmt_str(sp_err_str(err))); } sp_sys_close(fd); diff --git a/test/sys/root.c b/test/sys/root.c index 6f2810f3..bbb67cc5 100644 --- a/test/sys/root.c +++ b/test/sys/root.c @@ -3,19 +3,40 @@ UTEST_EMPTY_FIXTURE(sys_root) UTEST_F(sys_root, out_of_range_returns_invalid) { - SKIP_ON_WASM() EXPECT_EQ(sp_sys_get_root(1024), SP_SYS_INVALID_FD); } -UTEST_F(sys_root, root_zero_is_named_dot) { - SKIP_ON_WASM() +UTEST_F(sys_root, out_of_range_has_no_name) { c8 buf [8] = sp_zero; - EXPECT_EQ(sp_sys_get_root_name(0, buf, sizeof(buf)), 1); - EXPECT_STREQ(buf, "."); + u64 len = 0; + EXPECT_EQ(sp_sys_get_root_name(1024, buf, sizeof(buf), &len), SP_ERR_SYS_BAD_FD); } -UTEST_F(sys_root, out_of_range_has_no_name) { +UTEST_F(sys_root, root_zero_has_no_name) { SKIP_ON_WASM() c8 buf [8] = sp_zero; - EXPECT_EQ(sp_sys_get_root_name(1024, buf, sizeof(buf)), -1); + u64 len = 0; + EXPECT_EQ(sp_sys_get_root_name(0, buf, sizeof(buf), &len), SP_ERR_SYS_UNSUPPORTED); +} + +UTEST_F(sys_root, every_root_is_named_or_unsupported) { + c8 buf [SP_PATH_MAX]; + sp_for(it, 1024) { + if (sp_sys_get_root(it) == SP_SYS_INVALID_FD) break; + u64 len = 0; + sp_err_t err = sp_sys_get_root_name(it, buf, sizeof(buf), &len); + EXPECT_TRUE(err == SP_OK || err == SP_ERR_SYS_UNSUPPORTED); + if (err == SP_OK) EXPECT_TRUE(buf[len] == 0); + } +} + +UTEST_F(sys_root, root_zero_has_a_path) { + sp_sys_fd_t root = sp_sys_get_root(0); + if (root == SP_SYS_INVALID_FD) UTEST_SKIP("no roots"); + + c8 buf [SP_PATH_MAX] = sp_zero; + u64 len = 0; + EXPECT_EQ(sp_sys_get_fd_path(root, buf, sizeof(buf), &len), SP_OK); + EXPECT_TRUE(len > 0); + EXPECT_TRUE(buf[len] == 0); } diff --git a/test/sys/self_path.c b/test/sys/self_path.c index 654542b1..cfaab91d 100644 --- a/test/sys/self_path.c +++ b/test/sys/self_path.c @@ -20,10 +20,19 @@ static sp_err_t run(sp_test_t* t, test_t* c) { c8 buf [SP_PATH_MAX]; sp_for(it, sizeof(buf)) buf[it] = (c8)0xAB; - s64 n = c->fn == PATH_CWD ? - sp_sys_get_cwd_path(buf, sizeof(buf)) : - sp_sys_get_exe_path(buf, sizeof(buf)); - if (n <= 0) return sp_test_skip(t, "not available"); + u64 n = 0; + switch (c->fn) { + case PATH_CWD: { + if (sp_sys_get_fd_path(sp_sys_get_root(0), buf, sizeof(buf), &n)) return sp_test_skip(t, "not available"); + break; + } + case PATH_EXE: { + s64 rc = sp_sys_get_exe_path(buf, sizeof(buf)); + if (rc <= 0) return sp_test_skip(t, "not available"); + n = (u64)rc; + break; + } + } if (buf[n] != 0) { sp_test_fail(t, "not NUL-terminated"); From c7dca2529fc82b27898db3d1cad271d757e71ca0 Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Sat, 26 Sep 2026 21:07:35 -0400 Subject: [PATCH 33/72] fixit --- example/filesystem.c | 2 +- example/io.c | 2 +- example/zero_copy.c | 2 +- sp.h | 75 +++++++++---------- sp/sp_msvc.h | 4 +- test/cli/complete.c | 5 +- test/fs/at_root.c | 165 ------------------------------------------ test/fs/get_cwd.c | 3 + test/fs/resolve.c | 168 +++++++++++++++++++++++++++++++++++++++++++ test/sys/root.c | 24 ------- test/sys/self_path.c | 4 +- 11 files changed, 218 insertions(+), 236 deletions(-) delete mode 100644 test/fs/at_root.c create mode 100644 test/fs/resolve.c diff --git a/example/filesystem.c b/example/filesystem.c index 6be573fe..f15b5497 100644 --- a/example/filesystem.c +++ b/example/filesystem.c @@ -70,7 +70,7 @@ sp_cli_result_t command(sp_cli_t* cli) { args_t args = ctx->args; sp_str_t dir = sp_str_empty(args.dir) ? sp_str_lit(".") : args.dir; - sp_path_t path = sp_path_at_root(dir); + sp_path_t path = sp_path_resolve(dir); if (args.walk) { walk(dir, path); diff --git a/example/io.c b/example/io.c index f16af49c..cae09c09 100644 --- a/example/io.c +++ b/example/io.c @@ -24,7 +24,7 @@ s32 run(s32 num_args, const c8** args) { // Or, if you already have a file descriptor, wrap it. sp_io will close file descriptors // for you automatically if you pass SP_IO_CLOSE_MODE_AUTO sp_sys_fd_t fd = SP_SYS_INVALID_FD; - sp_sys_open_s(sp_sys_get_root(0), exe, SP_SYS_OPEN_MODE_RO, 0, &fd); + sp_sys_open_s(sp_fs_get_cwd(), exe, SP_SYS_OPEN_MODE_RO, 0, &fd); sp_io_file_reader_from_file(&r, (sp_sys_fd_t)fd, SP_IO_CLOSE_MODE_AUTO); sp_io_read(&r.base, buffer.data, buffer.capacity, &buffer.len); sp_log("sp_io_file_reader_from_file: {}", sp_fmt_str(sp_mem_buffer_as_str(&buffer))); diff --git a/example/zero_copy.c b/example/zero_copy.c index d3f10bbf..d948e871 100644 --- a/example/zero_copy.c +++ b/example/zero_copy.c @@ -78,7 +78,7 @@ static run_t copy_run(sp_str_t src_path, sp_str_t dst_path, src_t src, sink_t si } case SRC_STREAM_FILE: { sp_sys_fd_t fd = SP_SYS_INVALID_FD; - if (sp_sys_open_s(sp_sys_get_root(0), src_path, SP_SYS_OPEN_MODE_RO, 0, &fd) != SP_OK) { + if (sp_sys_open_s(sp_fs_get_cwd(), src_path, SP_SYS_OPEN_MODE_RO, 0, &fd) != SP_OK) { return sp_zero_s(run_t); } sp_io_stream_reader_from_file(&sr, fd, SP_IO_CLOSE_MODE_AUTO); diff --git a/sp.h b/sp.h index 6baf0840..6d8038d3 100644 --- a/sp.h +++ b/sp.h @@ -1456,7 +1456,6 @@ SP_API sp_err_t sp_sys_pwrite(sp_sys_fd_t fd, const void* buf, u64 count, u64 SP_API sp_err_t sp_sys_transfer(sp_sys_fd_t in, u64* in_pos, sp_sys_fd_t out, u64 count, u64* bytes_moved); SP_API sp_err_t sp_sys_transfer_positional(sp_sys_fd_t in, u64* in_pos, sp_sys_fd_t out, u64 count, u64 offset, u64* bytes_moved); SP_API sp_sys_fd_t sp_sys_get_root(s32 it); -SP_API sp_err_t sp_sys_get_root_name(s32 it, c8* buf, u64 size, u64* len); SP_API s64 sp_sys_get_exe_path(c8* buf, u64 size); SP_API s64 sp_sys_get_storage_path(c8* buf, u64 size); SP_API s64 sp_sys_get_config_path(c8* buf, u64 size); @@ -1555,7 +1554,6 @@ typedef struct { sp_err_t (*transfer)(sp_sys_fd_t in, u64* in_pos, sp_sys_fd_t out, u64 count, u64* bytes_moved); sp_err_t (*transfer_positional)(sp_sys_fd_t in, u64* in_pos, sp_sys_fd_t out, u64 count, u64 offset, u64* bytes_moved); sp_sys_fd_t (*get_root)(s32 it); - sp_err_t (*get_root_name)(s32 it, c8* buf, u64 size, u64* len); s64 (*get_exe_path)(c8* buf, u64 size); s64 (*get_storage_path)(c8* buf, u64 size); s64 (*get_config_path)(c8* buf, u64 size); @@ -1630,7 +1628,6 @@ SP_API sp_err_t sp_sys_pwrite_p(sp_sys_fd_t fd, const void* buf, u64 count, u SP_API sp_err_t sp_sys_transfer_p(sp_sys_fd_t in, u64* in_pos, sp_sys_fd_t out, u64 count, u64* bytes_moved); SP_API sp_err_t sp_sys_transfer_positional_p(sp_sys_fd_t in, u64* in_pos, sp_sys_fd_t out, u64 count, u64 offset, u64* bytes_moved); SP_API sp_sys_fd_t sp_sys_get_root_p(s32 it); -SP_API sp_err_t sp_sys_get_root_name_p(s32 it, c8* buf, u64 size, u64* len); SP_API s64 sp_sys_get_exe_path_p(c8* buf, u64 size); SP_API s64 sp_sys_get_storage_path_p(c8* buf, u64 size); SP_API s64 sp_sys_get_config_path_p(c8* buf, u64 size); @@ -3111,8 +3108,10 @@ SP_API sp_err_t sp_fs_create_sym_link_at(sp_str_t target, sp_path_t SP_API sp_err_t sp_fs_copy_at(sp_path_t from, sp_path_t to, sp_fs_atomic_mode_t mode); SP_API sp_err_t sp_fs_copy_file_at(sp_path_t from, sp_path_t to, sp_fs_atomic_mode_t mode); SP_API sp_err_t sp_fs_copy_tree_at(sp_path_t from, sp_path_t to, sp_fs_atomic_mode_t mode); -SP_API sp_sys_fd_t sp_fs_get_cwd(void); +SP_API sp_sys_fd_t sp_fs_get_cwd(); SP_API sp_path_t sp_path_at_root(sp_str_t sub); +SP_API sp_path_t sp_path_resolve(sp_str_t path); +SP_API sp_path_t sp_path_resolve_for(sp_str_t path, sp_fs_path_kind_t kind); // literally sys+normalize, if normalize isnt needed this isn't either? SP_API sp_str_t sp_fs_canonicalize_path(sp_mem_t mem, sp_str_t path); @@ -5720,7 +5719,6 @@ const sp_sys_vtable_t sp_sys_vtable_platform = { .transfer = sp_sys_transfer_p, .transfer_positional = sp_sys_transfer_positional_p, .get_root = sp_sys_get_root_p, - .get_root_name = sp_sys_get_root_name_p, .get_exe_path = sp_sys_get_exe_path_p, .get_storage_path = sp_sys_get_storage_path_p, .get_config_path = sp_sys_get_config_path_p, @@ -5895,10 +5893,6 @@ sp_sys_fd_t sp_sys_get_root(s32 it) { return (sp_rt.vt->get_root)(it); } -sp_err_t sp_sys_get_root_name(s32 it, c8* buf, u64 size, u64* len) { - return (sp_rt.vt->get_root_name)(it, buf, size, len); -} - s64 sp_sys_get_exe_path(c8* buf, u64 size) { return (sp_rt.vt->get_exe_path)(buf, size); } @@ -8701,22 +8695,6 @@ sp_sys_fd_t sp_sys_get_root_p(s32 it) { #endif } -sp_err_t sp_sys_get_root_name_p(s32 it, c8* buf, u64 size, u64* len) { - *len = 0; - -#if defined(SP_WIN32) || defined(SP_LINUX) || defined(SP_MACOS) || defined(SP_COSMO) - (void)buf; (void)size; - if (it != 0) return SP_ERR_SYS_BAD_FD; - return SP_ERR_SYS_UNSUPPORTED; - -#elif defined(SP_WASM) - return sp_sys_wasi_prestat_name((__wasi_fd_t)(3 + it), buf, size, len); - -#else - #error "sp_sys_get_root_name" -#endif -} - ////////////////////// // SP_SYS_NANOSLEEP // ////////////////////// @@ -16831,7 +16809,7 @@ void sp_fmon_os_add_dir(sp_fmon_t* monitor, sp_str_t path) { sp_sys_fd_t handle = SP_SYS_INVALID_FD; if (!SP_NT_SUCCESS(sp_sys_nt_open( - sp_sys_get_root(0), + sp_fs_get_cwd(), path, FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, @@ -20118,36 +20096,53 @@ sp_str_t sp_fs_replace_ext(sp_mem_t mem, sp_str_t path, sp_str_t ext) { sp_str_join(mem, stripped, ext, sp_str_lit(".")); } -sp_sys_fd_t sp_fs_get_cwd(void) { +sp_sys_fd_t sp_fs_get_cwd() { if (sp_rt.cwd != SP_SYS_INVALID_FD) return sp_rt.cwd; return sp_sys_get_root(0); } sp_path_t sp_path_at_root(sp_str_t sub) { - sp_path_t best = { .dir = sp_fs_get_cwd(), .sub = sub }; - if (!sp_fs_is_absolute(sub)) return best; + return (sp_path_t) { .dir = sp_fs_get_cwd(), .sub = sub }; +} + +SP_PRIVATE bool sp_fs_is_sep_for(c8 c, sp_fs_path_kind_t kind) { + switch (kind) { + case SP_FS_PATH_POSIX: return c == '/'; + case SP_FS_PATH_WINDOWS: return c == '/' || c == '\\'; + } + SP_UNREACHABLE_RETURN(false); +} + +sp_path_t sp_path_resolve_for(sp_str_t path, sp_fs_path_kind_t kind) { + sp_path_t best = sp_path_at_root(path); + if (!sp_fs_is_absolute_for(path, kind)) return best; u32 longest = 0; for (s32 it = 0;; it++) { - sp_sys_fd_t fd = sp_sys_get_root(it); - if (fd == SP_SYS_INVALID_FD) return best; + sp_sys_fd_t root = sp_sys_get_root(it); + if (root == SP_SYS_INVALID_FD) return best; c8 buf [SP_PATH_MAX]; u64 len = 0; - if (sp_sys_get_root_name(it, buf, SP_PATH_MAX, &len)) continue; + if (sp_sys_get_fd_path(root, buf, SP_PATH_MAX, &len)) continue; - sp_str_t name = sp_fs_trim_path(sp_str(buf, (u32)len)); - if (name.len <= longest) continue; - if (!sp_str_starts_with(sub, name)) continue; - if (name.len < sub.len && !sp_fs_is_sep(sub.data[name.len]) && !sp_fs_is_sep(sp_str_back(name))) continue; + sp_str_t label = sp_str(buf, (u32)len); + while (label.len > 1 && sp_fs_is_sep_for(sp_str_back(label), kind)) label.len--; + if (label.len <= longest) continue; + if (!sp_str_starts_with(path, label)) continue; + if (label.len < path.len && !sp_fs_is_sep_for(path.data[label.len], kind) && !sp_fs_is_sep_for(sp_str_back(label), kind)) continue; - sp_str_t rest = sp_str_suffix(sub, (s32)(sub.len - name.len)); - while (!sp_str_empty(rest) && sp_fs_is_sep(rest.data[0])) rest = sp_str_suffix(rest, (s32)rest.len - 1); - longest = name.len; - best = (sp_path_t) { .dir = fd, .sub = sp_str_empty(rest) ? sp_str_lit(".") : rest }; + sp_str_t rest = sp_str_suffix(path, (s32)(path.len - label.len)); + while (!sp_str_empty(rest) && sp_fs_is_sep_for(rest.data[0], kind)) rest = sp_str_suffix(rest, (s32)rest.len - 1); + longest = label.len; + best = (sp_path_t) { .dir = root, .sub = sp_str_empty(rest) ? sp_str_lit(".") : rest }; } } +sp_path_t sp_path_resolve(sp_str_t path) { + return sp_path_resolve_for(path, sp_os_get_path_kind()); +} + sp_fs_kind_t sp_fs_get_link_kind_at(sp_path_t path) { if (sp_str_empty(path.sub)) return SP_FS_KIND_NONE; sp_sys_file_meta_t st = sp_zero; diff --git a/sp/sp_msvc.h b/sp/sp_msvc.h index d2d5d1c6..8d30f493 100644 --- a/sp/sp_msvc.h +++ b/sp/sp_msvc.h @@ -453,7 +453,7 @@ static sp_msvc_err_t sp_msvc_find_sdks(sp_msvc_t* msvc, sp_msvc_arch_t arch) { SP_ALIGNED u8 dir_buf [SP_SYS_DIR_MIN_BUF]; sp_fs_dir_t dir = sp_zero; - if (sp_fs_dir_open(&dir, sp_sys_get_root(0), lib, 0, sp_mem_slice(dir_buf, sizeof(dir_buf)))) { + if (sp_fs_dir_open(&dir, sp_fs_get_cwd(), lib, 0, sp_mem_slice(dir_buf, sizeof(dir_buf)))) { return SP_MSVC_ERR_SDK_NOT_FOUND; } @@ -500,7 +500,7 @@ static sp_msvc_err_t sp_msvc_find_installations(sp_msvc_t* msvc, sp_msvc_arch_t SP_ALIGNED u8 dir_buf [SP_SYS_DIR_MIN_BUF]; sp_fs_dir_t dir = sp_zero; - if (sp_fs_dir_open(&dir, sp_sys_get_root(0), instances.value, 0, sp_mem_slice(dir_buf, sizeof(dir_buf)))) { + if (sp_fs_dir_open(&dir, sp_fs_get_cwd(), instances.value, 0, sp_mem_slice(dir_buf, sizeof(dir_buf)))) { return SP_MSVC_ERR_VS_NOT_FOUND; } diff --git a/test/cli/complete.c b/test/cli/complete.c index 960b3d7d..55b8afa0 100644 --- a/test/cli/complete.c +++ b/test/cli/complete.c @@ -408,9 +408,12 @@ static void run_cli_completer_path_test(s32* utest_result, sp_mem_t mem, cli_com }, buffer, sizeof(buffer)); if (t.expect.anchored) { + sp_sys_fd_t dir = sp_fs_get_cwd(); + if (dir == SP_SYS_INVALID_FD) UTEST_SKIP("no cwd"); + c8 cwd [SP_PATH_MAX]; u64 cwd_len = 0; - sp_err_t err = sp_sys_get_fd_path(sp_fs_get_cwd(), cwd, sizeof(cwd), &cwd_len); + sp_err_t err = sp_sys_get_fd_path(dir, cwd, sizeof(cwd), &cwd_len); EXPECT_EQ(err, SP_OK); if (err) return; sp_str_t expected = sp_fmt(mem, "{}/{}", sp_fmt_str(sp_str(cwd, sp_cast(u32, cwd_len))), sp_fmt_cstr(t.arg0)).value; diff --git a/test/fs/at_root.c b/test/fs/at_root.c deleted file mode 100644 index 55110086..00000000 --- a/test/fs/at_root.c +++ /dev/null @@ -1,165 +0,0 @@ -#include "sp.h" -#include "sp/sp_test.h" - -#define AT_ROOT_MAX_ROOTS 4 -#define AT_ROOT_FD_BASE 9000 - -typedef struct { - const c8* name; - sp_err_t err; -} at_root_root_t; - -typedef struct { - s32 root; - bool invalid; - const c8* sub; -} at_root_expect_t; - -typedef struct { - const c8* name; - at_root_root_t roots [AT_ROOT_MAX_ROOTS]; - const c8* sub; - at_root_expect_t expect; -} at_root_test_t; - -static const at_root_test_t* active; - -static s32 num_roots(void) { - s32 n = 0; - sp_carr_for(active->roots, it) { - if (!active->roots[it].name && !active->roots[it].err) break; - n++; - } - return n; -} - -static sp_sys_fd_t fake_get_root(s32 it) { - if (it < 0 || it >= num_roots()) return SP_SYS_INVALID_FD; - return (sp_sys_fd_t)(AT_ROOT_FD_BASE + it); -} - -static sp_err_t fake_get_root_name(s32 it, c8* buf, u64 size, u64* len) { - *len = 0; - if (it < 0 || it >= num_roots()) return SP_ERR_SYS_BAD_FD; - - const at_root_root_t* root = &active->roots[it]; - if (root->err) return root->err; - - u64 n = sp_cstr_len(root->name); - if (n >= size) return SP_ERR_SYS_NAME_TOO_LONG; - sp_mem_copy(buf, root->name, n); - buf[n] = 0; - *len = n; - return SP_OK; -} - -static const at_root_test_t tests [] = { - { - .name = "unlabeled_root_takes_relative", - .roots = { { .err = SP_ERR_SYS_UNSUPPORTED } }, - .sub = "x/y", - .expect = { .sub = "x/y" }, - }, - { - .name = "unlabeled_root_takes_absolute", - .roots = { { .err = SP_ERR_SYS_UNSUPPORTED } }, - .sub = "/etc/x", - .expect = { .sub = "/etc/x" }, - }, - { - .name = "relative_ignores_labels", - .roots = { { .name = "." }, { .name = "/cfg" } }, - .sub = "cfg/a", - .expect = { .sub = "cfg/a" }, - }, - { - .name = "label_prefix_is_stripped", - .roots = { { .name = "/bar" } }, - .sub = "/bar/x", - .expect = { .sub = "x" }, - }, - { - .name = "exact_label_is_dot", - .roots = { { .name = "/bar" } }, - .sub = "/bar", - .expect = { .sub = "." }, - }, - { - .name = "trailing_separator_is_dot", - .roots = { { .name = "/bar" } }, - .sub = "/bar/", - .expect = { .sub = "." }, - }, - { - .name = "label_matches_whole_components", - .roots = { { .name = "/bar" } }, - .sub = "/barn/x", - .expect = { .sub = "/barn/x" }, - }, - { - .name = "longest_label_wins", - .roots = { { .name = "/bar" }, { .name = "/bar/x" } }, - .sub = "/bar/x/y", - .expect = { .root = 1, .sub = "y" }, - }, - { - .name = "longest_label_wins_regardless_of_order", - .roots = { { .name = "/bar/x" }, { .name = "/bar" } }, - .sub = "/bar/x/y", - .expect = { .sub = "y" }, - }, - { - .name = "slash_label_takes_everything", - .roots = { { .name = "/" } }, - .sub = "/x/y", - .expect = { .sub = "x/y" }, - }, - { - .name = "label_trailing_separator_is_ignored", - .roots = { { .name = "/bar/" } }, - .sub = "/bar/x", - .expect = { .sub = "x" }, - }, - { - .name = "dot_label_does_not_match_absolute", - .roots = { { .name = "." }, { .name = "/cfg" } }, - .sub = "/cfg/a", - .expect = { .root = 1, .sub = "a" }, - }, - { - .name = "unmatched_absolute_falls_back_to_cwd", - .roots = { { .name = "." }, { .name = "/cfg" } }, - .sub = "/other", - .expect = { .sub = "/other" }, - }, - { - .name = "unlabeled_root_never_wins", - .roots = { { .err = SP_ERR_SYS_UNSUPPORTED }, { .name = "/bar" } }, - .sub = "/bar/x", - .expect = { .root = 1, .sub = "x" }, - }, - { - .name = "no_roots_is_invalid", - .sub = "x", - .expect = { .invalid = true, .sub = "x" }, - }, -}; - -static sp_err_t run(sp_test_t* t, at_root_test_t* c) { - sp_sys_vtable_t vt = sp_sys_vtable_platform; - vt.get_root = fake_get_root; - vt.get_root_name = fake_get_root_name; - - active = c; - const sp_sys_vtable_t* saved = sp_sys_set_vtable(&vt); - sp_path_t path = sp_path_at_root(sp_cstr_as_str(c->sub)); - sp_sys_set_vtable(saved); - active = SP_NULLPTR; - - sp_sys_fd_t dir = c->expect.invalid ? SP_SYS_INVALID_FD : (sp_sys_fd_t)(AT_ROOT_FD_BASE + c->expect.root); - sp_expect_eq(t, path.dir, dir); - sp_expect_str_eq(t, path.sub, sp_cstr_as_str(c->expect.sub)); - return SP_OK; -} - -sp_test_each_fn(fs, at_root, at_root_test_t, tests, run, .serial = true); diff --git a/test/fs/get_cwd.c b/test/fs/get_cwd.c index d459378a..6846a590 100644 --- a/test/fs/get_cwd.c +++ b/test/fs/get_cwd.c @@ -52,12 +52,15 @@ sp_test(fs, get_cwd_override, .serial = true) { bool found = sp_fs_is_file(sp_str_lit("A")); sp_str_t cwd_path = sp_fs_get_cwd_path(sp_test_arena(t)); sp_rt.cwd = SP_SYS_INVALID_FD; + sp_sys_fd_t restored = sp_fs_get_cwd(); sp_sys_close(dir); sp_expect_eq(t, cwd, dir); sp_expect_eq(t, path.dir, dir); + sp_expect_str_eq(t, path.sub, sp_str_lit("A")); sp_expect(t, found); sp_expect_str_eq(t, cwd_path, sp_fs_canonicalize_path(sp_test_arena(t), sp_test_dir(t))); + sp_expect_eq(t, restored, sp_sys_get_root(0)); return SP_OK; } diff --git a/test/fs/resolve.c b/test/fs/resolve.c new file mode 100644 index 00000000..b19a4589 --- /dev/null +++ b/test/fs/resolve.c @@ -0,0 +1,168 @@ +#include "sp.h" +#include "sp/sp_test.h" + +#define MAX_ROOTS 4 +#define FD_BASE 9000 + +typedef struct { + const c8* label; + sp_err_t err; +} root_t; + +typedef struct { + s32 root; + bool invalid; + const c8* sub; +} expect_t; + +typedef struct { + const c8* name; + sp_fs_path_kind_t kind; + root_t roots [MAX_ROOTS]; + const c8* path; + expect_t expect; +} test_t; + +static const test_t* active; + +static s32 num_roots() { + s32 n = 0; + sp_carr_for(active->roots, it) { + if (!active->roots[it].label && !active->roots[it].err) break; + n++; + } + return n; +} + +static sp_sys_fd_t get_root(s32 it) { + if (it < 0 || it >= num_roots()) return SP_SYS_INVALID_FD; + return (sp_sys_fd_t)(FD_BASE + it); +} + +static sp_err_t get_fd_path(sp_sys_fd_t fd, c8* buf, u64 size, u64* len) { + *len = 0; + s32 it = (s32)fd - FD_BASE; + if (it < 0 || it >= num_roots()) return SP_ERR_SYS_BAD_FD; + + const root_t* root = &active->roots[it]; + if (root->err) return root->err; + + u64 n = sp_cstr_len(root->label); + if (n >= size) return SP_ERR_SYS_NAME_TOO_LONG; + sp_mem_copy(buf, root->label, n); + buf[n] = 0; + *len = n; + return SP_OK; +} + +static const test_t tests [] = { + { + .name = "relative_goes_to_cwd", + .roots = { { .label = "/A" } }, + .path = "A/B", + .expect = { .sub = "A/B" }, + }, + { + .name = "label_is_stripped", + .roots = { { .label = "/B" }, { .label = "/A" } }, + .path = "/A/B", + .expect = { .root = 1, .sub = "B" }, + }, + { + .name = "exact_label_is_dot", + .roots = { { .label = "/B" }, { .label = "/A" } }, + .path = "/A", + .expect = { .root = 1, .sub = "." }, + }, + { + .name = "trailing_separator_is_dot", + .roots = { { .label = "/B" }, { .label = "/A" } }, + .path = "/A/", + .expect = { .root = 1, .sub = "." }, + }, + { + .name = "label_trailing_separator_is_ignored", + .roots = { { .label = "/B" }, { .label = "/A/" } }, + .path = "/A/B", + .expect = { .root = 1, .sub = "B" }, + }, + { + .name = "label_matches_whole_components", + .roots = { { .label = "/B" }, { .label = "/A" } }, + .path = "/AB/C", + .expect = { .sub = "/AB/C" }, + }, + { + .name = "longest_label_wins", + .roots = { { .label = "/A" }, { .label = "/A/B" } }, + .path = "/A/B/C", + .expect = { .root = 1, .sub = "C" }, + }, + { + .name = "longest_label_wins_in_any_order", + .roots = { { .label = "/A/B" }, { .label = "/A" } }, + .path = "/A/B/C", + .expect = { .sub = "C" }, + }, + { + .name = "slash_label_takes_everything", + .roots = { { .label = "/" } }, + .path = "/A/B", + .expect = { .sub = "A/B" }, + }, + { + .name = "relative_label_never_matches", + .roots = { { .label = "A" }, { .label = "/A" } }, + .path = "/A/B", + .expect = { .root = 1, .sub = "B" }, + }, + { + .name = "unmatched_absolute_goes_to_cwd", + .roots = { { .label = "/B" }, { .label = "/A" } }, + .path = "/C", + .expect = { .sub = "/C" }, + }, + { + .name = "unreadable_root_is_skipped", + .roots = { { .err = SP_ERR_SYS_BAD_FD }, { .label = "/A" } }, + .path = "/A/B", + .expect = { .root = 1, .sub = "B" }, + }, + { + .name = "no_roots", + .path = "A", + .expect = { .invalid = true, .sub = "A" }, + }, + { + .name = "backslash_is_a_name_byte_on_posix", + .roots = { { .label = "/B" }, { .label = "/A" } }, + .path = "/A\\B", + .expect = { .sub = "/A\\B" }, + }, + { + .name = "backslash_separates_on_windows", + .kind = SP_FS_PATH_WINDOWS, + .roots = { { .label = "C:/B" }, { .label = "C:/A" } }, + .path = "C:/A\\B", + .expect = { .root = 1, .sub = "B" }, + }, +}; + +static sp_err_t run(sp_test_t* t, test_t* c) { + sp_sys_vtable_t vt = sp_sys_vtable_platform; + vt.get_root = get_root; + vt.get_fd_path = get_fd_path; + + active = c; + const sp_sys_vtable_t* saved = sp_sys_set_vtable(&vt); + sp_path_t path = sp_path_resolve_for(sp_cstr_as_str(c->path), c->kind); + sp_sys_set_vtable(saved); + active = SP_NULLPTR; + + sp_sys_fd_t dir = c->expect.invalid ? SP_SYS_INVALID_FD : (sp_sys_fd_t)(FD_BASE + c->expect.root); + sp_expect_eq(t, path.dir, dir); + sp_expect_str_eq(t, path.sub, sp_cstr_as_str(c->expect.sub)); + return SP_OK; +} + +sp_test_each_fn(fs, resolve, test_t, tests, run, .serial = true); diff --git a/test/sys/root.c b/test/sys/root.c index bbb67cc5..88185772 100644 --- a/test/sys/root.c +++ b/test/sys/root.c @@ -6,30 +6,6 @@ UTEST_F(sys_root, out_of_range_returns_invalid) { EXPECT_EQ(sp_sys_get_root(1024), SP_SYS_INVALID_FD); } -UTEST_F(sys_root, out_of_range_has_no_name) { - c8 buf [8] = sp_zero; - u64 len = 0; - EXPECT_EQ(sp_sys_get_root_name(1024, buf, sizeof(buf), &len), SP_ERR_SYS_BAD_FD); -} - -UTEST_F(sys_root, root_zero_has_no_name) { - SKIP_ON_WASM() - c8 buf [8] = sp_zero; - u64 len = 0; - EXPECT_EQ(sp_sys_get_root_name(0, buf, sizeof(buf), &len), SP_ERR_SYS_UNSUPPORTED); -} - -UTEST_F(sys_root, every_root_is_named_or_unsupported) { - c8 buf [SP_PATH_MAX]; - sp_for(it, 1024) { - if (sp_sys_get_root(it) == SP_SYS_INVALID_FD) break; - u64 len = 0; - sp_err_t err = sp_sys_get_root_name(it, buf, sizeof(buf), &len); - EXPECT_TRUE(err == SP_OK || err == SP_ERR_SYS_UNSUPPORTED); - if (err == SP_OK) EXPECT_TRUE(buf[len] == 0); - } -} - UTEST_F(sys_root, root_zero_has_a_path) { sp_sys_fd_t root = sp_sys_get_root(0); if (root == SP_SYS_INVALID_FD) UTEST_SKIP("no roots"); diff --git a/test/sys/self_path.c b/test/sys/self_path.c index cfaab91d..86b4b238 100644 --- a/test/sys/self_path.c +++ b/test/sys/self_path.c @@ -23,7 +23,9 @@ static sp_err_t run(sp_test_t* t, test_t* c) { u64 n = 0; switch (c->fn) { case PATH_CWD: { - if (sp_sys_get_fd_path(sp_sys_get_root(0), buf, sizeof(buf), &n)) return sp_test_skip(t, "not available"); + sp_sys_fd_t root = sp_sys_get_root(0); + if (root == SP_SYS_INVALID_FD) return sp_test_skip(t, "no roots"); + sp_must_ok(t, sp_sys_get_fd_path(root, buf, sizeof(buf), &n)); break; } case PATH_EXE: { From 40cfdeb9735068c7e432675bcdb9d564c96c40f6 Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Sun, 27 Sep 2026 01:56:02 -0400 Subject: [PATCH 34/72] crappy fs examples --- Makefile | 7 ++-- example/fs/absolute.c | 29 ++++++++++++++ example/fs/config.c | 27 +++++++++++++ example/{filesystem.c => fs/iterator.c} | 0 example/fs/ls.c | 51 ++++++++++++++++++++++++ example/fs/preopen.c | 25 ++++++++++++ example/fs/roots.c | 37 +++++++++++++++++ example/fs/workspace.c | 53 +++++++++++++++++++++++++ 8 files changed, 226 insertions(+), 3 deletions(-) create mode 100644 example/fs/absolute.c create mode 100644 example/fs/config.c rename example/{filesystem.c => fs/iterator.c} (100%) create mode 100644 example/fs/ls.c create mode 100644 example/fs/preopen.c create mode 100644 example/fs/roots.c create mode 100644 example/fs/workspace.c diff --git a/Makefile b/Makefile index e170956e..5c481903 100644 --- a/Makefile +++ b/Makefile @@ -109,7 +109,8 @@ endif TESTS = amalg app array asset cli etc env format fmon glob ht io math process ps rb str sys thread time mem prompt leak qsort term BENCHES = glob heap -EXAMPLES = app array cargo cli format hash_table io zero_copy ls palette post prompt prompt_fancy serve signal tls wc filesystem +EXAMPLES = app array cargo cli format hash_table io zero_copy ls palette post prompt prompt_fancy serve signal tls wc +EXAMPLES += fs/absolute fs/config fs/iterator fs/ls fs/preopen fs/roots fs/workspace TRIPLES = \ x86_64-linux-none x86_64-linux-gnu x86_64-linux-musl \ aarch64-linux-none aarch64-linux-gnu aarch64-linux-musl \ @@ -148,7 +149,7 @@ $(EXAMPLE_DIR)/post$(EXE): example/post.c $(SP_HEADERS) $(MBEDTLS_LIB) | $(EXAMP endif -$(EXAMPLE_DIR)/%$(EXE): example/%.c $(SP_HEADERS) | $(EXAMPLE_DIR) +$(EXAMPLE_DIR)/%$(EXE): example/%.c $(SP_HEADERS) | $(EXAMPLE_DIR) $(EXAMPLE_DIR)/fs $(CC) $(CFLAGS) -I. -o $@ $< $(LDLIBS_PLATFORM) $(TEST_DIR)/%$(EXE): test/%.c $(SP_HEADERS) $(TEST_SOURCES) | $(TEST_DIR) @@ -178,7 +179,7 @@ wasm: +$(MAKE) wasm32-wasi wasm32-freestanding +$(MAKE) MODE=cpp wasm32-wasi wasm32-freestanding -$(BUILD_DIR) $(EXAMPLE_DIR) $(TEST_DIR) $(BENCH_DIR) $(BUILD_DIR)/mbedtls: +$(BUILD_DIR) $(EXAMPLE_DIR) $(EXAMPLE_DIR)/fs $(TEST_DIR) $(BENCH_DIR) $(BUILD_DIR)/mbedtls: mkdir -p $@ clean: diff --git a/example/fs/absolute.c b/example/fs/absolute.c new file mode 100644 index 00000000..fa6d19e3 --- /dev/null +++ b/example/fs/absolute.c @@ -0,0 +1,29 @@ +#define SP_IMPLEMENTATION +#include "sp.h" + +static sp_err_t save(sp_mem_t mem, sp_str_t dir, sp_str_t file, sp_str_t* settings) { + sp_try(sp_fs_create_dir_at(sp_path_resolve(dir))); + sp_try(sp_fs_write_atomic_at(sp_path_resolve(file), sp_str_lit("theme = \"dark\"\n"))); + return sp_io_read_file_at(mem, sp_path_resolve(file), settings); +} + +s32 run(s32 num_args, const c8** args) { + sp_mem_arena_marker_t s = sp_mem_begin_scratch(); + + sp_str_t config = sp_fs_get_config_path(s.mem); + sp_str_t dir = sp_fs_join_path(s.mem, config, sp_str_lit("sp")); + sp_str_t file = sp_fs_join_path(s.mem, dir, sp_str_lit("settings.toml")); + sp_str_t settings = sp_zero; + sp_err_t err = sp_str_empty(config) ? SP_ERR_SYS_NOT_FOUND : save(s.mem, dir, file, &settings); + if (err) { + sp_log("{.red}: {}", sp_fmt_str(file), sp_fmt_str(sp_err_str(err))); + } + else { + sp_log("{.cyan}", sp_fmt_str(file)); + sp_print("{}", sp_fmt_str(settings)); + } + + sp_mem_end_scratch(s); + return err ? 1 : 0; +} +SP_MAIN(run) diff --git a/example/fs/config.c b/example/fs/config.c new file mode 100644 index 00000000..2cfc094f --- /dev/null +++ b/example/fs/config.c @@ -0,0 +1,27 @@ +#define SP_IMPLEMENTATION +#include "sp.h" + +static sp_err_t load(sp_mem_t mem, sp_str_t path, sp_str_t* config) { + if (!sp_fs_exists(path)) { + sp_try(sp_fs_create_file_str(path, sp_str_lit("name = \"sp\"\n"))); + } + return sp_io_read_file(mem, path, config); +} + +s32 run(s32 num_args, const c8** args) { + sp_mem_arena_marker_t s = sp_mem_begin_scratch(); + + sp_str_t path = sp_str_lit("app.toml"); + sp_str_t config = sp_zero; + sp_err_t err = load(s.mem, path, &config); + if (err) { + sp_log("{.red}: {}", sp_fmt_str(path), sp_fmt_str(sp_err_str(err))); + } + else { + sp_print("{}", sp_fmt_str(config)); + } + + sp_mem_end_scratch(s); + return err ? 1 : 0; +} +SP_MAIN(run) diff --git a/example/filesystem.c b/example/fs/iterator.c similarity index 100% rename from example/filesystem.c rename to example/fs/iterator.c diff --git a/example/fs/ls.c b/example/fs/ls.c new file mode 100644 index 00000000..c5d7bc30 --- /dev/null +++ b/example/fs/ls.c @@ -0,0 +1,51 @@ +#define SP_IMPLEMENTATION +#include "sp.h" + +static void print_entry(sp_mem_t mem, sp_fs_it_t* it) { + sp_sys_file_meta_t meta = sp_zero; + sp_sys_get_link_metadata_s(it->at.dir, it->at.sub, &meta); + + sp_tm_epoch_t modified = { .s = (u64)meta.mtime.tv_sec, .ns = (u32)meta.mtime.tv_nsec }; + sp_print("{:>10} {.gray} ", sp_fmt_int(meta.size), sp_fmt_str(sp_tm_epoch_to_iso8601(mem, modified))); + + switch (it->entry.kind) { + case SP_FS_KIND_DIR: { + sp_log("{.blue}", sp_fmt_str(it->entry.name)); + break; + } + case SP_FS_KIND_FILE: { + sp_log("{}", sp_fmt_str(it->entry.name)); + break; + } + case SP_FS_KIND_SYMLINK: { + c8 buf [SP_PATH_MAX]; + sp_str_t target = sp_zero; + sp_sys_readlink_s(it->at.dir, it->at.sub, buf, sizeof(buf), &target); + sp_log("{.cyan} -> {}", sp_fmt_str(it->entry.name), sp_fmt_str(target)); + break; + } + case SP_FS_KIND_NONE: { + break; + } + } +} + +s32 run(s32 num_args, const c8** args) { + sp_str_t dir = num_args > 1 ? sp_cstr_as_str(args[1]) : sp_str_lit("."); + + sp_mem_arena_marker_t s = sp_mem_begin_scratch(); + sp_fs_it_t it = sp_fs_it_new_at(s.mem, sp_path_resolve(dir), 0); + while (sp_fs_it_next(&it)) { + print_entry(s.mem, &it); + } + + sp_err_t err = it.err; + if (err) { + sp_log("{.red}: {}", sp_fmt_str(dir), sp_fmt_str(sp_err_str(err))); + } + + sp_fs_it_deinit(&it); + sp_mem_end_scratch(s); + return err ? 1 : 0; +} +SP_MAIN(run) diff --git a/example/fs/preopen.c b/example/fs/preopen.c new file mode 100644 index 00000000..2fd0d259 --- /dev/null +++ b/example/fs/preopen.c @@ -0,0 +1,25 @@ +#define SP_IMPLEMENTATION +#include "sp.h" + +static sp_err_t start(sp_mem_t mem, sp_path_t config, sp_path_t state) { + sp_str_t content = sp_zero; + sp_try(sp_io_read_file_at(mem, config, &content)); + sp_print("{}", sp_fmt_str(content)); + + return sp_fs_write_atomic_at(state, sp_tm_epoch_to_iso8601(mem, sp_tm_now_epoch())); +} + +s32 run(s32 num_args, const c8** args) { + sp_mem_arena_marker_t s = sp_mem_begin_scratch(); + + sp_path_t config = sp_path_resolve(sp_str_lit("/config/app.toml")); + sp_path_t state = sp_path_resolve(sp_str_lit("/data/last_run")); + sp_err_t err = start(s.mem, config, state); + if (err) { + sp_log("{.red}", sp_fmt_str(sp_err_str(err))); + } + + sp_mem_end_scratch(s); + return err ? 1 : 0; +} +SP_MAIN(run) diff --git a/example/fs/roots.c b/example/fs/roots.c new file mode 100644 index 00000000..e64e1a20 --- /dev/null +++ b/example/fs/roots.c @@ -0,0 +1,37 @@ +#define SP_IMPLEMENTATION +#include "sp.h" + +static void print_root(sp_sys_fd_t root) { + c8 buf [SP_PATH_MAX]; + u64 len = 0; + sp_err_t err = sp_sys_get_fd_path(root, buf, sizeof(buf), &len); + if (err) { + sp_log("{:>4} {.red}", sp_fmt_int(root), sp_fmt_str(sp_err_str(err))); + } + else { + sp_log("{:>4} {.cyan}", sp_fmt_int(root), sp_fmt_str(sp_str(buf, (u32)len))); + } +} + +static void print_path(sp_str_t path) { + sp_path_t resolved = sp_path_resolve(path); + sp_log( + "{:>4} {} {.gray} {}", + sp_fmt_int(resolved.dir), + sp_fmt_str(resolved.sub), + sp_fmt_cstr("<-"), + sp_fmt_str(path) + ); +} + +s32 run(s32 num_args, const c8** args) { + for (s32 it = 0; sp_sys_get_root(it) != SP_SYS_INVALID_FD; it++) { + print_root(sp_sys_get_root(it)); + } + + sp_for_range(it, 1, (u32)num_args) { + print_path(sp_cstr_as_str(args[it])); + } + return 0; +} +SP_MAIN(run) diff --git a/example/fs/workspace.c b/example/fs/workspace.c new file mode 100644 index 00000000..b3e9e8ba --- /dev/null +++ b/example/fs/workspace.c @@ -0,0 +1,53 @@ +#define SP_IMPLEMENTATION +#include "sp.h" + +static sp_path_t in(sp_sys_fd_t dir, const c8* sub) { + return (sp_path_t) { .dir = dir, .sub = sp_cstr_as_str(sub) }; +} + +static sp_err_t list(sp_sys_fd_t dir) { + sp_mem_arena_marker_t s = sp_mem_begin_scratch(); + sp_fs_it_t it = sp_fs_it_new_at(s.mem, in(dir, "."), 0); + while (sp_fs_it_walk(&it)) { + sp_log("{}", sp_fmt_str(it.entry.rel)); + } + + sp_err_t err = it.err; + sp_fs_it_deinit(&it); + sp_mem_end_scratch(s); + return err; +} + +static sp_err_t build(sp_sys_fd_t dir) { + sp_try(sp_fs_create_dir_at(in(dir, "src/nested"))); + sp_try(sp_fs_create_file_cstr_at(in(dir, "src/A"), "A")); + sp_try(sp_fs_create_file_cstr_at(in(dir, "src/nested/B"), "B")); + sp_try(sp_fs_copy_at(in(dir, "src"), in(dir, "dst"), SP_FS_ATOMIC_REPLACE)); + sp_try(sp_fs_remove_dir_at(in(dir, "src"))); + return list(dir); +} + +static sp_err_t work(sp_path_t path) { + sp_try(sp_fs_create_dir_at(path)); + + sp_sys_fd_t dir = SP_SYS_INVALID_FD; + sp_err_t err = sp_sys_open_dir_s(path.dir, path.sub, 0, &dir); + if (!err) { + err = build(dir); + sp_sys_close(dir); + } + + sp_err_t removed = sp_fs_remove_dir_at(path); + return err ? err : removed; +} + +s32 run(s32 num_args, const c8** args) { + sp_str_t name = num_args > 1 ? sp_cstr_as_str(args[1]) : sp_str_lit("workspace"); + + sp_err_t err = work(sp_path_resolve(name)); + if (err) { + sp_log("{.red}: {}", sp_fmt_str(name), sp_fmt_str(sp_err_str(err))); + } + return err ? 1 : 0; +} +SP_MAIN(run) From 7939e144cebcd0b5c58d27f46cc15dc506d62312 Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Sun, 27 Sep 2026 03:08:45 -0400 Subject: [PATCH 35/72] more basic 1:1 wasi --- sp.h | 120 ++++++++++++++++++++++++++++++++---------- test/sys/perms.c | 2 + test/sys/timestamps.c | 1 + 3 files changed, 95 insertions(+), 28 deletions(-) diff --git a/sp.h b/sp.h index 6d8038d3..3af80ed5 100644 --- a/sp.h +++ b/sp.h @@ -6511,8 +6511,10 @@ SP_PRIVATE u32 sp_sys_trim_path(const c8* path, u32 len) { #define SP_WASI_ENOTDIR 54 #define SP_WASI_ENOTEMPTY 55 #define SP_WASI_ENOTSUP 58 +#define SP_WASI_EOVERFLOW 61 #define SP_WASI_EPERM 63 #define SP_WASI_EPIPE 64 +#define SP_WASI_ERANGE 68 #define SP_WASI_EROFS 69 #define SP_WASI_ESPIPE 70 #define SP_WASI_ETIMEDOUT 73 @@ -6570,6 +6572,13 @@ SP_PRIVATE sp_err_t sp_sys_wasi_prestat_name(__wasi_fd_t fd, c8* buf, u64 size, *len = prestat.u.dir.pr_name_len; return SP_OK; } + +SP_PRIVATE sp_err_t sp_sys_wasi_path(const c8* path, u32* len) { + *len = sp_sys_trim_path(path, *len); + if (!*len) return SP_ERR_SYS_NOT_FOUND; + if (*len >= SP_PATH_MAX) return SP_ERR_SYS_NAME_TOO_LONG; + return SP_OK; +} #endif @@ -7646,8 +7655,9 @@ sp_err_t sp_sys_rename_p(sp_sys_fd_t from_fd, const c8* from, u32 from_len, sp_s return sp_sys_err_from_libc(renameat(from_fd, buffers.from, to_fd, buffers.to)); #elif defined(SP_WASM) - (void)from_fd; (void)from; (void)from_len; (void)to_fd; (void)to; (void)to_len; - return SP_ERR_SYS_UNSUPPORTED; + sp_try(sp_sys_wasi_path(from, &from_len)); + sp_try(sp_sys_wasi_path(to, &to_len)); + return sp_sys_err_from_wasi(sp_wasi_path_rename((__wasi_fd_t)from_fd, from, from_len, (__wasi_fd_t)to_fd, to, to_len)); #else #error "sp_sys_rename" @@ -8572,7 +8582,7 @@ sp_err_t sp_sys_open_p(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_open_mode return SP_OK; #elif defined(SP_WASM) - len = sp_sys_trim_path(path, len); + sp_try(sp_sys_wasi_path(path, &len)); __wasi_rights_t rights = sp_sys_wasi_open_rights(mode); __wasi_fdflags_t fdflags = (flags & SP_SYS_OPEN_APPEND) ? __WASI_FDFLAGS_APPEND : 0; __wasi_fd_t opened = -1; @@ -8647,7 +8657,7 @@ sp_err_t sp_sys_open_dir_p(sp_sys_fd_t fd, const c8* path, u32 len, u32 flags, s return SP_OK; #elif defined(SP_WASM) - len = sp_sys_trim_path(path, len); + sp_try(sp_sys_wasi_path(path, &len)); __wasi_lookupflags_t lookup = (flags & SP_SYS_OPEN_DIR_NO_FOLLOW) ? 0 : __WASI_LOOKUPFLAGS_SYMLINK_FOLLOW; __wasi_rights_t inheriting = SP_SYS_WASI_DIR_RIGHTS | sp_sys_wasi_open_rights(SP_SYS_OPEN_MODE_RW); __wasi_fd_t opened = -1; @@ -10471,7 +10481,7 @@ sp_err_t sp_sys_get_path_metadata_p(sp_sys_fd_t fd, const c8* path, u32 len, sp_ return SP_OK; #elif defined(SP_WASM) - len = sp_sys_trim_path(path, len); + sp_try(sp_sys_wasi_path(path, &len)); __wasi_filestat_t native = sp_zero; sp_try(sp_sys_err_from_wasi(sp_wasi_path_filestat_get((__wasi_fd_t)fd, __WASI_LOOKUPFLAGS_SYMLINK_FOLLOW, path, len, &native))); sp_sys_file_meta_from_wasi(&native, st); @@ -10510,7 +10520,7 @@ sp_err_t sp_sys_get_link_metadata_p(sp_sys_fd_t fd, const c8* path, u32 len, sp_ return SP_OK; #elif defined(SP_WASM) - len = sp_sys_trim_path(path, len); + sp_try(sp_sys_wasi_path(path, &len)); __wasi_filestat_t native = sp_zero; sp_try(sp_sys_err_from_wasi(sp_wasi_path_filestat_get((__wasi_fd_t)fd, 0, path, len, &native))); sp_sys_file_meta_from_wasi(&native, st); @@ -10557,8 +10567,9 @@ sp_err_t sp_sys_mkdir_p(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_file_per return sp_sys_err_from_libc(mkdirat(fd, buf, (mode_t)(perms.value & 07777))); #elif defined(SP_WASM) - (void)fd; (void)path; (void)len; (void)perms; - return SP_ERR_SYS_UNSUPPORTED; + (void)perms; + sp_try(sp_sys_wasi_path(path, &len)); + return sp_sys_err_from_wasi(sp_wasi_path_create_directory((__wasi_fd_t)fd, path, len)); #else #error "sp_sys_mkdir" @@ -10587,8 +10598,8 @@ sp_err_t sp_sys_rmdir_p(sp_sys_fd_t fd, const c8* path, u32 len) { return sp_sys_err_from_libc(unlinkat(fd, buf, AT_REMOVEDIR)); #elif defined(SP_WASM) - (void)fd; (void)path; (void)len; - return SP_ERR_SYS_UNSUPPORTED; + sp_try(sp_sys_wasi_path(path, &len)); + return sp_sys_err_from_wasi(sp_wasi_path_remove_directory((__wasi_fd_t)fd, path, len)); #else #error "sp_sys_rmdir" @@ -10627,8 +10638,8 @@ sp_err_t sp_sys_unlink_p(sp_sys_fd_t fd, const c8* path, u32 len) { return sp_sys_err_from_libc(rc); #elif defined(SP_WASM) - (void)fd; (void)path; (void)len; - return SP_ERR_SYS_UNSUPPORTED; + sp_try(sp_sys_wasi_path(path, &len)); + return sp_sys_err_from_wasi(sp_wasi_path_unlink_file((__wasi_fd_t)fd, path, len)); #else #error "sp_sys_unlink" @@ -10714,8 +10725,9 @@ sp_err_t sp_sys_link_p(sp_sys_fd_t from_fd, const c8* existing, u32 existing_len return sp_sys_err_from_libc(linkat(from_fd, buffers.existing, to_fd, buffers.alias, 0)); #elif defined(SP_WASM) - (void)from_fd; (void)existing; (void)existing_len; (void)to_fd; (void)alias; (void)alias_len; - return SP_ERR_SYS_UNSUPPORTED; + sp_try(sp_sys_wasi_path(existing, &existing_len)); + sp_try(sp_sys_wasi_path(alias, &alias_len)); + return sp_sys_err_from_wasi(sp_wasi_path_link((__wasi_fd_t)from_fd, 0, existing, existing_len, (__wasi_fd_t)to_fd, alias, alias_len)); #else #error "sp_sys_link" @@ -10848,8 +10860,10 @@ sp_err_t sp_sys_symlink_p(const c8* existing, u32 existing_len, sp_sys_fd_t to_f return sp_sys_err_from_libc(symlinkat(buffers.existing, to_fd, buffers.alias)); #elif defined(SP_WASM) - (void)existing; (void)existing_len; (void)to_fd; (void)alias; (void)alias_len; - return SP_ERR_SYS_UNSUPPORTED; + if (!existing_len) return SP_ERR_SYS_NOT_FOUND; + if (existing_len >= SP_PATH_MAX) return SP_ERR_SYS_NAME_TOO_LONG; + sp_try(sp_sys_wasi_path(alias, &alias_len)); + return sp_sys_err_from_wasi(sp_wasi_path_symlink(existing, existing_len, (__wasi_fd_t)to_fd, alias, alias_len)); #else #error "sp_sys_symlink" @@ -10929,8 +10943,18 @@ sp_err_t sp_sys_readlink_p(sp_sys_fd_t fd, const c8* path, u32 len, c8* buf, u64 return SP_OK; #elif defined(SP_WASM) - (void)fd; (void)path; (void)len; (void)buf; (void)size; - return SP_ERR_SYS_UNSUPPORTED; + sp_try(sp_sys_wasi_path(path, &len)); + __wasi_size_t used = 0; + __wasi_errno_t err = sp_wasi_path_readlink((__wasi_fd_t)fd, path, len, (u8*)buf, (__wasi_size_t)size, &used); + switch (err) { + case SP_WASI_ESUCCESS: break; + case SP_WASI_EOVERFLOW: + case SP_WASI_ERANGE: return SP_ERR_SYS_NAME_TOO_LONG; + default: return sp_sys_err_from_wasi(err); + } + if ((u64)used == size) return SP_ERR_SYS_NAME_TOO_LONG; + *target_len = (u64)used; + return SP_OK; #else #error "sp_sys_readlink" @@ -13736,6 +13760,18 @@ SP_PRIVATE sp_fs_kind_t sp_sys_dir_dtype_to_kind(u8 d_type) { } return SP_FS_KIND_NONE; } + +#elif defined(SP_WASM) +sp_static_assert(SP_SYS_DIR_MIN_BUF >= sizeof(__wasi_dirent_t) + 255, sp_sys_dir_min_buf); + +SP_PRIVATE sp_fs_kind_t sp_sys_dir_dtype_to_kind(u8 d_type) { + switch (d_type) { + case __WASI_FILETYPE_REGULAR_FILE: { return SP_FS_KIND_FILE; } + case __WASI_FILETYPE_DIRECTORY: { return SP_FS_KIND_DIR; } + case __WASI_FILETYPE_SYMBOLIC_LINK: { return SP_FS_KIND_SYMLINK; } + } + return SP_FS_KIND_NONE; +} #endif sp_err_t sp_sys_dir_it_open_p(sp_sys_fd_t fd, sp_sys_dir_it_t* out) { @@ -13751,7 +13787,8 @@ sp_err_t sp_sys_dir_it_open_p(sp_sys_fd_t fd, sp_sys_dir_it_t* out) { out->state = (s64)(intptr_t)dir; return SP_OK; #elif defined(SP_WASM) - return SP_ERR_SYS_UNSUPPORTED; + out->state = (s64)__WASI_DIRCOOKIE_START; + return SP_OK; #else #error "sp_sys_dir_it_open" #endif @@ -13787,8 +13824,23 @@ sp_err_t sp_sys_dir_it_read_p(sp_sys_dir_it_t* it, sp_mem_buffer_t* buf) { buf->len = d->d_reclen; return SP_OK; #elif defined(SP_WASM) - (void)it; - return SP_ERR_SYS_UNSUPPORTED; + // @spader this is just a temporary stub! probably wrong + __wasi_size_t used = 0; + __wasi_errno_t err = sp_wasi_fd_readdir((__wasi_fd_t)it->fd, buf->data, (__wasi_size_t)buf->capacity, (__wasi_dircookie_t)it->state, &used); + if (err == SP_WASI_ENOENT) return SP_OK; + if (err) return sp_sys_err_from_wasi(err); + + u64 end = 0; + while (end + sizeof(__wasi_dirent_t) <= used) { + __wasi_dirent_t d; + sp_mem_copy(&d, buf->data + end, sizeof(d)); + if (end + sizeof(d) + d.d_namlen > used) break; + end += sizeof(d) + d.d_namlen; + it->state = (s64)d.d_next; + } + if (used && !end) return SP_ERR_SYS_NAME_TOO_LONG; + buf->len = end; + return SP_OK; #else #error "sp_sys_dir_it_read" #endif @@ -13829,22 +13881,28 @@ sp_err_t sp_sys_dir_it_parse_p(sp_sys_dir_it_t* it, sp_mem_buffer_t* buf, u64* c out->kind = sp_sys_dir_dtype_to_kind(d->d_type); return SP_OK; #elif defined(SP_WASM) - (void)it; (void)buf; (void)cursor; - return SP_ERR_SYS_UNSUPPORTED; + // @spader this is just a temporary stub! probably wrong + (void)it; + __wasi_dirent_t d; + sp_mem_copy(&d, buf->data + *cursor, sizeof(d)); + *cursor += sizeof(d); + + out->name = sp_ptr_cast(const c8*, buf->data + *cursor); + out->len = d.d_namlen; + out->kind = sp_sys_dir_dtype_to_kind(d.d_type); + *cursor += d.d_namlen; + return SP_OK; #else #error "sp_sys_dir_it_parse" #endif } sp_err_t sp_sys_dir_it_close_p(sp_sys_dir_it_t* it) { -#if defined(SP_WIN32) || defined(SP_LINUX) +#if defined(SP_WIN32) || defined(SP_LINUX) || defined(SP_WASM) return sp_sys_close(it->fd); #elif defined(SP_MACOS) || defined(SP_COSMO) if (closedir((DIR*)(intptr_t)it->state)) return sp_sys_err_from_errno(errno); return SP_OK; -#elif defined(SP_WASM) - (void)it; - return SP_ERR_SYS_UNSUPPORTED; #else #error "sp_sys_dir_it_close" #endif @@ -20909,7 +20967,13 @@ sp_err_t sp_fs_copy_file_at(sp_path_t from, sp_path_t to, sp_fs_atomic_mode_t mo err = sp_io_copy(sp_fs_atomic_writer(&af), &reader.base, SP_NULLPTR); sp_io_file_reader_close(&reader); - if (!err) err = sp_sys_set_file_perms_s(af.dir, af.temp, meta.perms); + + // @spader I'm not sure if eating this error is right. My gut says that it + // is not. On WASM, the copy could work, but you simply can't set perms. + if (!err) { + err = sp_sys_set_file_perms_s(af.dir, af.temp, meta.perms); + if (err == SP_ERR_SYS_UNSUPPORTED) err = SP_OK; + } if (err) { sp_fs_atomic_abort(&af); return err; diff --git a/test/sys/perms.c b/test/sys/perms.c index 01af08fc..5ab29fd7 100644 --- a/test/sys/perms.c +++ b/test/sys/perms.c @@ -35,6 +35,7 @@ static const test_t tests [] = { }; static sp_err_t run(sp_test_t* t, test_t* it) { + sp_test_skip_on_wasm(); sp_str_t path = sp_fs_join_path(sp_test_arena(t), sp_test_dir(t), sp_str_lit("A")); sp_fs_create_file(path); sp_sys_fd_t root = sp_sys_get_root(0); @@ -89,6 +90,7 @@ sp_test(sys, default_perms_then_read_only) { sp_test(sys, mkdir_applies_perms) { sp_test_skip_on_win32(); + sp_test_skip_on_wasm(); sp_str_t path = sp_fs_join_path(sp_test_arena(t), sp_test_dir(t), sp_str_lit("D")); sp_sys_fd_t root = sp_sys_get_root(0); diff --git a/test/sys/timestamps.c b/test/sys/timestamps.c index a2af087d..07d3fb3a 100644 --- a/test/sys/timestamps.c +++ b/test/sys/timestamps.c @@ -53,6 +53,7 @@ static sp_err_t run(sp_test_t* t, test_t* c) { break; } case OP_SET_PERMS: { + sp_test_skip_on_wasm(); sp_sys_file_perms_t perms = before.perms; sp_sys_set_read_only(&perms, true); sp_must_ok(t, sp_sys_set_file_perms_s(root, path, perms)); From c27c3648d516242fa0d2d30eddeb56572b9e07b9 Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Sun, 27 Sep 2026 03:38:19 -0400 Subject: [PATCH 36/72] fs: stub root labels, host kind path helpers --- sp.h | 161 ++++++++++++++++++++++++++-------------------- test/fs/get_cwd.c | 6 +- test/fs/resolve.c | 7 +- 3 files changed, 98 insertions(+), 76 deletions(-) diff --git a/sp.h b/sp.h index 3af80ed5..384de5e9 100644 --- a/sp.h +++ b/sp.h @@ -1456,6 +1456,7 @@ SP_API sp_err_t sp_sys_pwrite(sp_sys_fd_t fd, const void* buf, u64 count, u64 SP_API sp_err_t sp_sys_transfer(sp_sys_fd_t in, u64* in_pos, sp_sys_fd_t out, u64 count, u64* bytes_moved); SP_API sp_err_t sp_sys_transfer_positional(sp_sys_fd_t in, u64* in_pos, sp_sys_fd_t out, u64 count, u64 offset, u64* bytes_moved); SP_API sp_sys_fd_t sp_sys_get_root(s32 it); +SP_API sp_err_t sp_sys_get_root_label(s32 it, c8* buf, u64 size, u64* len); SP_API s64 sp_sys_get_exe_path(c8* buf, u64 size); SP_API s64 sp_sys_get_storage_path(c8* buf, u64 size); SP_API s64 sp_sys_get_config_path(c8* buf, u64 size); @@ -1554,6 +1555,7 @@ typedef struct { sp_err_t (*transfer)(sp_sys_fd_t in, u64* in_pos, sp_sys_fd_t out, u64 count, u64* bytes_moved); sp_err_t (*transfer_positional)(sp_sys_fd_t in, u64* in_pos, sp_sys_fd_t out, u64 count, u64 offset, u64* bytes_moved); sp_sys_fd_t (*get_root)(s32 it); + sp_err_t (*get_root_label)(s32 it, c8* buf, u64 size, u64* len); s64 (*get_exe_path)(c8* buf, u64 size); s64 (*get_storage_path)(c8* buf, u64 size); s64 (*get_config_path)(c8* buf, u64 size); @@ -1628,6 +1630,7 @@ SP_API sp_err_t sp_sys_pwrite_p(sp_sys_fd_t fd, const void* buf, u64 count, u SP_API sp_err_t sp_sys_transfer_p(sp_sys_fd_t in, u64* in_pos, sp_sys_fd_t out, u64 count, u64* bytes_moved); SP_API sp_err_t sp_sys_transfer_positional_p(sp_sys_fd_t in, u64* in_pos, sp_sys_fd_t out, u64 count, u64 offset, u64* bytes_moved); SP_API sp_sys_fd_t sp_sys_get_root_p(s32 it); +SP_API sp_err_t sp_sys_get_root_label_p(s32 it, c8* buf, u64 size, u64* len); SP_API s64 sp_sys_get_exe_path_p(c8* buf, u64 size); SP_API s64 sp_sys_get_storage_path_p(c8* buf, u64 size); SP_API s64 sp_sys_get_config_path_p(c8* buf, u64 size); @@ -3053,20 +3056,29 @@ typedef struct { sp_fs_kind_t kind; } sp_fs_dir_entry_t; -SP_API sp_str_t sp_fs_get_name(sp_str_t path); -SP_API sp_str_t sp_fs_get_name_for(sp_str_t path, sp_fs_path_kind_t kind); -SP_API sp_str_t sp_fs_parent_path(sp_str_t path); -SP_API sp_str_t sp_fs_trim_path(sp_str_t path); -SP_API sp_str_t sp_fs_normalize_path(sp_mem_t mem, sp_str_t path); -SP_API sp_str_t sp_fs_get_ext(sp_str_t path); -SP_API sp_str_t sp_fs_get_stem(sp_str_t path); SP_API bool sp_fs_is_sep(c8 c); +SP_API bool sp_fs_is_sep_for(c8 c, sp_fs_path_kind_t kind); SP_API bool sp_fs_is_root(sp_str_t path); +SP_API bool sp_fs_is_root_for(sp_str_t path, sp_fs_path_kind_t kind); SP_API bool sp_fs_is_absolute(sp_str_t path); SP_API bool sp_fs_is_absolute_for(sp_str_t path, sp_fs_path_kind_t kind); SP_API bool sp_fs_is_absolute_w(sp_wide_str_t path); -SP_API sp_str_t sp_fs_join_path(sp_mem_t mem, sp_str_t a, sp_str_t b); +SP_API sp_str_t sp_fs_trim_path(sp_str_t path); +SP_API sp_str_t sp_fs_trim_path_for(sp_str_t path, sp_fs_path_kind_t kind); +SP_API sp_str_t sp_fs_get_name(sp_str_t path); +SP_API sp_str_t sp_fs_get_name_for(sp_str_t path, sp_fs_path_kind_t kind); +SP_API sp_str_t sp_fs_parent_path(sp_str_t path); +SP_API sp_str_t sp_fs_parent_path_for(sp_str_t path, sp_fs_path_kind_t kind); +SP_API sp_str_t sp_fs_get_ext(sp_str_t path); +SP_API sp_str_t sp_fs_get_ext_for(sp_str_t path, sp_fs_path_kind_t kind); +SP_API sp_str_t sp_fs_get_stem(sp_str_t path); +SP_API sp_str_t sp_fs_get_stem_for(sp_str_t path, sp_fs_path_kind_t kind); SP_API sp_str_t sp_fs_replace_ext(sp_mem_t mem, sp_str_t path, sp_str_t ext); +SP_API sp_str_t sp_fs_replace_ext_for(sp_mem_t mem, sp_str_t path, sp_str_t ext, sp_fs_path_kind_t kind); +SP_API sp_str_t sp_fs_join_path(sp_mem_t mem, sp_str_t a, sp_str_t b); +SP_API sp_str_t sp_fs_join_path_for(sp_mem_t mem, sp_str_t a, sp_str_t b, sp_fs_path_kind_t kind); +SP_API sp_str_t sp_fs_normalize_path(sp_mem_t mem, sp_str_t path); +SP_API sp_str_t sp_fs_normalize_path_for(sp_mem_t mem, sp_str_t path, sp_fs_path_kind_t kind); SP_API sp_err_t sp_fs_collect(sp_mem_t mem, sp_str_t path, sp_da(sp_fs_entry_t)* out); SP_API sp_err_t sp_fs_collect_recursive(sp_mem_t mem, sp_str_t path, sp_da(sp_fs_entry_t)* out); SP_API bool sp_fs_exists(sp_str_t path); @@ -3109,9 +3121,8 @@ SP_API sp_err_t sp_fs_copy_at(sp_path_t from, sp_path_t to, sp_fs_at SP_API sp_err_t sp_fs_copy_file_at(sp_path_t from, sp_path_t to, sp_fs_atomic_mode_t mode); SP_API sp_err_t sp_fs_copy_tree_at(sp_path_t from, sp_path_t to, sp_fs_atomic_mode_t mode); SP_API sp_sys_fd_t sp_fs_get_cwd(); -SP_API sp_path_t sp_path_at_root(sp_str_t sub); +SP_API sp_path_t sp_path_at_cwd(sp_str_t sub); SP_API sp_path_t sp_path_resolve(sp_str_t path); -SP_API sp_path_t sp_path_resolve_for(sp_str_t path, sp_fs_path_kind_t kind); // literally sys+normalize, if normalize isnt needed this isn't either? SP_API sp_str_t sp_fs_canonicalize_path(sp_mem_t mem, sp_str_t path); @@ -5719,6 +5730,7 @@ const sp_sys_vtable_t sp_sys_vtable_platform = { .transfer = sp_sys_transfer_p, .transfer_positional = sp_sys_transfer_positional_p, .get_root = sp_sys_get_root_p, + .get_root_label = sp_sys_get_root_label_p, .get_exe_path = sp_sys_get_exe_path_p, .get_storage_path = sp_sys_get_storage_path_p, .get_config_path = sp_sys_get_config_path_p, @@ -5893,6 +5905,10 @@ sp_sys_fd_t sp_sys_get_root(s32 it) { return (sp_rt.vt->get_root)(it); } +sp_err_t sp_sys_get_root_label(s32 it, c8* buf, u64 size, u64* len) { + return (sp_rt.vt->get_root_label)(it, buf, size, len); +} + s64 sp_sys_get_exe_path(c8* buf, u64 size) { return (sp_rt.vt->get_exe_path)(buf, size); } @@ -8705,6 +8721,11 @@ sp_sys_fd_t sp_sys_get_root_p(s32 it) { #endif } +sp_err_t sp_sys_get_root_label_p(s32 it, c8* buf, u64 size, u64* len) { + *len = 0; + return SP_ERR_SYS_UNSUPPORTED; +} + ////////////////////// // SP_SYS_NANOSLEEP // ////////////////////// @@ -17629,7 +17650,7 @@ sp_err_t sp_io_file_reader_from_path_at(sp_io_file_reader_t* r, sp_path_t path) } sp_err_t sp_io_file_reader_from_path(sp_io_file_reader_t* r, sp_str_t path) { - return sp_io_file_reader_from_path_at(r, sp_path_at_root(path)); + return sp_io_file_reader_from_path_at(r, sp_path_at_cwd(path)); } sp_err_t sp_io_file_reader_seek(sp_io_file_reader_t* r, s64 offset, sp_io_whence_t whence, s64* position) { @@ -18306,7 +18327,7 @@ sp_err_t sp_io_file_writer_from_path_at(sp_io_file_writer_t* w, sp_path_t path) } sp_err_t sp_io_file_writer_from_path(sp_io_file_writer_t* w, sp_str_t path) { - return sp_io_file_writer_from_path_at(w, sp_path_at_root(path)); + return sp_io_file_writer_from_path_at(w, sp_path_at_cwd(path)); } sp_err_t sp_io_writer_set_buffer(sp_io_writer_t* writer, u8* ptr, u64 size) { @@ -20159,46 +20180,48 @@ sp_sys_fd_t sp_fs_get_cwd() { return sp_sys_get_root(0); } -sp_path_t sp_path_at_root(sp_str_t sub) { - return (sp_path_t) { .dir = sp_fs_get_cwd(), .sub = sub }; +bool sp_fs_is_sep_for(c8 c, sp_fs_path_kind_t kind) { + return false; } -SP_PRIVATE bool sp_fs_is_sep_for(c8 c, sp_fs_path_kind_t kind) { - switch (kind) { - case SP_FS_PATH_POSIX: return c == '/'; - case SP_FS_PATH_WINDOWS: return c == '/' || c == '\\'; - } - SP_UNREACHABLE_RETURN(false); +bool sp_fs_is_root_for(sp_str_t path, sp_fs_path_kind_t kind) { + return false; } -sp_path_t sp_path_resolve_for(sp_str_t path, sp_fs_path_kind_t kind) { - sp_path_t best = sp_path_at_root(path); - if (!sp_fs_is_absolute_for(path, kind)) return best; +sp_str_t sp_fs_trim_path_for(sp_str_t path, sp_fs_path_kind_t kind) { + return sp_zero_s(sp_str_t); +} - u32 longest = 0; - for (s32 it = 0;; it++) { - sp_sys_fd_t root = sp_sys_get_root(it); - if (root == SP_SYS_INVALID_FD) return best; +sp_str_t sp_fs_parent_path_for(sp_str_t path, sp_fs_path_kind_t kind) { + return sp_zero_s(sp_str_t); +} - c8 buf [SP_PATH_MAX]; - u64 len = 0; - if (sp_sys_get_fd_path(root, buf, SP_PATH_MAX, &len)) continue; +sp_str_t sp_fs_get_ext_for(sp_str_t path, sp_fs_path_kind_t kind) { + return sp_zero_s(sp_str_t); +} - sp_str_t label = sp_str(buf, (u32)len); - while (label.len > 1 && sp_fs_is_sep_for(sp_str_back(label), kind)) label.len--; - if (label.len <= longest) continue; - if (!sp_str_starts_with(path, label)) continue; - if (label.len < path.len && !sp_fs_is_sep_for(path.data[label.len], kind) && !sp_fs_is_sep_for(sp_str_back(label), kind)) continue; +sp_str_t sp_fs_get_stem_for(sp_str_t path, sp_fs_path_kind_t kind) { + return sp_zero_s(sp_str_t); +} - sp_str_t rest = sp_str_suffix(path, (s32)(path.len - label.len)); - while (!sp_str_empty(rest) && sp_fs_is_sep_for(rest.data[0], kind)) rest = sp_str_suffix(rest, (s32)rest.len - 1); - longest = label.len; - best = (sp_path_t) { .dir = root, .sub = sp_str_empty(rest) ? sp_str_lit(".") : rest }; - } +sp_str_t sp_fs_replace_ext_for(sp_mem_t mem, sp_str_t path, sp_str_t ext, sp_fs_path_kind_t kind) { + return sp_zero_s(sp_str_t); +} + +sp_str_t sp_fs_join_path_for(sp_mem_t mem, sp_str_t a, sp_str_t b, sp_fs_path_kind_t kind) { + return sp_zero_s(sp_str_t); +} + +sp_str_t sp_fs_normalize_path_for(sp_mem_t mem, sp_str_t path, sp_fs_path_kind_t kind) { + return sp_zero_s(sp_str_t); +} + +sp_path_t sp_path_at_cwd(sp_str_t sub) { + return (sp_path_t) { .dir = sp_fs_get_cwd(), .sub = sub }; } sp_path_t sp_path_resolve(sp_str_t path) { - return sp_path_resolve_for(path, sp_os_get_path_kind()); + return sp_path_at_cwd(path); } sp_fs_kind_t sp_fs_get_link_kind_at(sp_path_t path) { @@ -20209,7 +20232,7 @@ sp_fs_kind_t sp_fs_get_link_kind_at(sp_path_t path) { } sp_fs_kind_t sp_fs_get_link_kind(sp_str_t path) { - return sp_fs_get_link_kind_at(sp_path_at_root(path)); + return sp_fs_get_link_kind_at(sp_path_at_cwd(path)); } sp_fs_kind_t sp_fs_get_target_kind_at(sp_path_t path) { @@ -20220,7 +20243,7 @@ sp_fs_kind_t sp_fs_get_target_kind_at(sp_path_t path) { } sp_fs_kind_t sp_fs_get_target_kind(sp_str_t path) { - return sp_fs_get_target_kind_at(sp_path_at_root(path)); + return sp_fs_get_target_kind_at(sp_path_at_cwd(path)); } bool sp_fs_exists_at(sp_path_t path) { @@ -20228,7 +20251,7 @@ bool sp_fs_exists_at(sp_path_t path) { } bool sp_fs_exists(sp_str_t path) { - return sp_fs_exists_at(sp_path_at_root(path)); + return sp_fs_exists_at(sp_path_at_cwd(path)); } bool sp_fs_is_file_at(sp_path_t path) { @@ -20236,7 +20259,7 @@ bool sp_fs_is_file_at(sp_path_t path) { } bool sp_fs_is_file(sp_str_t path) { - return sp_fs_is_file_at(sp_path_at_root(path)); + return sp_fs_is_file_at(sp_path_at_cwd(path)); } bool sp_fs_is_symlink_at(sp_path_t path) { @@ -20244,7 +20267,7 @@ bool sp_fs_is_symlink_at(sp_path_t path) { } bool sp_fs_is_symlink(sp_str_t path) { - return sp_fs_is_symlink_at(sp_path_at_root(path)); + return sp_fs_is_symlink_at(sp_path_at_cwd(path)); } bool sp_fs_is_dir_at(sp_path_t path) { @@ -20252,7 +20275,7 @@ bool sp_fs_is_dir_at(sp_path_t path) { } bool sp_fs_is_dir(sp_str_t path) { - return sp_fs_is_dir_at(sp_path_at_root(path)); + return sp_fs_is_dir_at(sp_path_at_cwd(path)); } bool sp_fs_is_target_file_at(sp_path_t path) { @@ -20260,7 +20283,7 @@ bool sp_fs_is_target_file_at(sp_path_t path) { } bool sp_fs_is_target_file(sp_str_t path) { - return sp_fs_is_target_file_at(sp_path_at_root(path)); + return sp_fs_is_target_file_at(sp_path_at_cwd(path)); } bool sp_fs_is_target_dir_at(sp_path_t path) { @@ -20268,7 +20291,7 @@ bool sp_fs_is_target_dir_at(sp_path_t path) { } bool sp_fs_is_target_dir(sp_str_t path) { - return sp_fs_is_target_dir_at(sp_path_at_root(path)); + return sp_fs_is_target_dir_at(sp_path_at_cwd(path)); } sp_fs_kind_t sp_fs_get_kind_at(sp_path_t path) { @@ -20276,7 +20299,7 @@ sp_fs_kind_t sp_fs_get_kind_at(sp_path_t path) { } sp_fs_kind_t sp_fs_get_kind(sp_str_t path) { - return sp_fs_get_kind_at(sp_path_at_root(path)); + return sp_fs_get_kind_at(sp_path_at_cwd(path)); } sp_tm_epoch_t sp_fs_get_mod_time_at(sp_path_t path) { @@ -20290,7 +20313,7 @@ sp_tm_epoch_t sp_fs_get_mod_time_at(sp_path_t path) { } sp_tm_epoch_t sp_fs_get_mod_time(sp_str_t path) { - return sp_fs_get_mod_time_at(sp_path_at_root(path)); + return sp_fs_get_mod_time_at(sp_path_at_cwd(path)); } SP_PRIVATE sp_str_t sp_fs_canonicalize_to(sp_path_t path, c8 buf [SP_PATH_MAX]) { @@ -20313,7 +20336,7 @@ sp_str_t sp_fs_canonicalize_path_at(sp_mem_t mem, sp_path_t path) { } sp_str_t sp_fs_canonicalize_path(sp_mem_t mem, sp_str_t path) { - return sp_fs_canonicalize_path_at(mem, sp_path_at_root(path)); + return sp_fs_canonicalize_path_at(mem, sp_path_at_cwd(path)); } sp_str_t sp_fs_get_exe_path(sp_mem_t mem) { @@ -20378,7 +20401,7 @@ sp_err_t sp_io_read_file_at(sp_mem_t mem, sp_path_t path, sp_str_t* content) { } sp_err_t sp_io_read_file(sp_mem_t mem, sp_str_t path, sp_str_t* content) { - return sp_io_read_file_at(mem, sp_path_at_root(path), content); + return sp_io_read_file_at(mem, sp_path_at_cwd(path), content); } SP_PRIVATE sp_err_t sp_fs_create_parent_at(sp_sys_fd_t dir, sp_str_t path); @@ -20402,7 +20425,7 @@ sp_err_t sp_fs_create_dir_at(sp_path_t path) { } sp_err_t sp_fs_create_dir(sp_str_t path) { - return sp_fs_create_dir_at(sp_path_at_root(path)); + return sp_fs_create_dir_at(sp_path_at_cwd(path)); } sp_err_t sp_fs_create_file_at(sp_path_t path) { @@ -20412,7 +20435,7 @@ sp_err_t sp_fs_create_file_at(sp_path_t path) { } sp_err_t sp_fs_create_file(sp_str_t path) { - return sp_fs_create_file_at(sp_path_at_root(path)); + return sp_fs_create_file_at(sp_path_at_cwd(path)); } sp_err_t sp_fs_create_file_slice_at(sp_path_t path, sp_mem_slice_t slice) { @@ -20424,7 +20447,7 @@ sp_err_t sp_fs_create_file_slice_at(sp_path_t path, sp_mem_slice_t slice) { } sp_err_t sp_fs_create_file_slice(sp_str_t path, sp_mem_slice_t slice) { - return sp_fs_create_file_slice_at(sp_path_at_root(path), slice); + return sp_fs_create_file_slice_at(sp_path_at_cwd(path), slice); } sp_err_t sp_fs_create_file_str_at(sp_path_t path, sp_str_t str) { @@ -20432,7 +20455,7 @@ sp_err_t sp_fs_create_file_str_at(sp_path_t path, sp_str_t str) { } sp_err_t sp_fs_create_file_str(sp_str_t path, sp_str_t str) { - return sp_fs_create_file_str_at(sp_path_at_root(path), str); + return sp_fs_create_file_str_at(sp_path_at_cwd(path), str); } sp_err_t sp_fs_create_file_cstr_at(sp_path_t path, const c8* str) { @@ -20440,7 +20463,7 @@ sp_err_t sp_fs_create_file_cstr_at(sp_path_t path, const c8* str) { } sp_err_t sp_fs_create_file_cstr(sp_str_t path, const c8* str) { - return sp_fs_create_file_cstr_at(sp_path_at_root(path), str); + return sp_fs_create_file_cstr_at(sp_path_at_cwd(path), str); } sp_err_t sp_fs_create_hard_link_at(sp_path_t target, sp_path_t link_path) { @@ -20448,7 +20471,7 @@ sp_err_t sp_fs_create_hard_link_at(sp_path_t target, sp_path_t link_path) { } sp_err_t sp_fs_create_hard_link(sp_str_t target, sp_str_t link_path) { - return sp_fs_create_hard_link_at(sp_path_at_root(target), sp_path_at_root(link_path)); + return sp_fs_create_hard_link_at(sp_path_at_cwd(target), sp_path_at_cwd(link_path)); } sp_err_t sp_fs_create_sym_link_at(sp_str_t target, sp_path_t link_path) { @@ -20456,7 +20479,7 @@ sp_err_t sp_fs_create_sym_link_at(sp_str_t target, sp_path_t link_path) { } sp_err_t sp_fs_create_sym_link(sp_str_t target, sp_str_t link_path) { - return sp_fs_create_sym_link_at(target, sp_path_at_root(link_path)); + return sp_fs_create_sym_link_at(target, sp_path_at_cwd(link_path)); } sp_err_t sp_fs_remove_file_at(sp_path_t path) { @@ -20464,7 +20487,7 @@ sp_err_t sp_fs_remove_file_at(sp_path_t path) { } sp_err_t sp_fs_remove_file(sp_str_t path) { - return sp_fs_remove_file_at(sp_path_at_root(path)); + return sp_fs_remove_file_at(sp_path_at_cwd(path)); } sp_err_t sp_fs_dir_open(sp_fs_dir_t* it, sp_sys_fd_t fd, sp_str_t path, u32 flags, sp_mem_slice_t buf) { @@ -20630,7 +20653,7 @@ sp_fs_it_t sp_fs_it_new_at(sp_mem_t mem, sp_path_t path, u32 flags) { } sp_fs_it_t sp_fs_it_new(sp_mem_t mem, sp_str_t path) { - return sp_fs_it_new_at(mem, sp_path_at_root(path), 0); + return sp_fs_it_new_at(mem, sp_path_at_cwd(path), 0); } SP_PRIVATE sp_fs_entry_t sp_fs_entry_copy(sp_mem_t mem, sp_fs_entry_t entry) { @@ -20664,7 +20687,7 @@ sp_err_t sp_fs_collect_recursive(sp_mem_t mem, sp_str_t path, sp_da(sp_fs_entry_ } sp_err_t sp_fs_remove_dir(sp_str_t path) { - return sp_fs_remove_dir_at(sp_path_at_root(path)); + return sp_fs_remove_dir_at(sp_path_at_cwd(path)); } /* @@ -20824,7 +20847,7 @@ sp_err_t sp_fs_atomic_open_at(sp_fs_atomic_t* af, sp_path_t path) { } sp_err_t sp_fs_atomic_open(sp_fs_atomic_t* af, sp_str_t path) { - return sp_fs_atomic_open_at(af, sp_path_at_root(path)); + return sp_fs_atomic_open_at(af, sp_path_at_cwd(path)); } sp_err_t sp_fs_atomic_open_staged_at(sp_fs_atomic_t* af, sp_path_t path, sp_str_t staging) { @@ -20833,7 +20856,7 @@ sp_err_t sp_fs_atomic_open_staged_at(sp_fs_atomic_t* af, sp_path_t path, sp_str_ } sp_err_t sp_fs_atomic_open_staged(sp_fs_atomic_t* af, sp_str_t path, sp_str_t staging) { - return sp_fs_atomic_open_staged_at(af, sp_path_at_root(path), staging); + return sp_fs_atomic_open_staged_at(af, sp_path_at_cwd(path), staging); } sp_io_writer_t* sp_fs_atomic_writer(sp_fs_atomic_t* af) { @@ -20890,7 +20913,7 @@ sp_err_t sp_fs_write_atomic_slice_staged_at(sp_path_t path, sp_str_t staging, sp } sp_err_t sp_fs_write_atomic_slice_staged(sp_str_t path, sp_str_t staging, sp_mem_slice_t slice) { - return sp_fs_write_atomic_slice_staged_at(sp_path_at_root(path), staging, slice); + return sp_fs_write_atomic_slice_staged_at(sp_path_at_cwd(path), staging, slice); } sp_err_t sp_fs_write_atomic_slice_at(sp_path_t path, sp_mem_slice_t slice) { @@ -20900,7 +20923,7 @@ sp_err_t sp_fs_write_atomic_slice_at(sp_path_t path, sp_mem_slice_t slice) { } sp_err_t sp_fs_write_atomic_slice(sp_str_t path, sp_mem_slice_t slice) { - return sp_fs_write_atomic_slice_at(sp_path_at_root(path), slice); + return sp_fs_write_atomic_slice_at(sp_path_at_cwd(path), slice); } sp_err_t sp_fs_write_atomic_at(sp_path_t path, sp_str_t str) { @@ -20910,7 +20933,7 @@ sp_err_t sp_fs_write_atomic_at(sp_path_t path, sp_str_t str) { } sp_err_t sp_fs_write_atomic(sp_str_t path, sp_str_t str) { - return sp_fs_write_atomic_at(sp_path_at_root(path), str); + return sp_fs_write_atomic_at(sp_path_at_cwd(path), str); } sp_err_t sp_fs_write_atomic_cstr_at(sp_path_t path, const c8* str) { @@ -20918,7 +20941,7 @@ sp_err_t sp_fs_write_atomic_cstr_at(sp_path_t path, const c8* str) { } sp_err_t sp_fs_write_atomic_cstr(sp_str_t path, const c8* str) { - return sp_fs_write_atomic_cstr_at(sp_path_at_root(path), str); + return sp_fs_write_atomic_cstr_at(sp_path_at_cwd(path), str); } SP_PRIVATE sp_err_t sp_fs_copy_link_at(sp_path_t from, sp_path_t to, sp_fs_atomic_mode_t mode) { diff --git a/test/fs/get_cwd.c b/test/fs/get_cwd.c index 6846a590..8bdadb9b 100644 --- a/test/fs/get_cwd.c +++ b/test/fs/get_cwd.c @@ -48,7 +48,7 @@ sp_test(fs, get_cwd_override, .serial = true) { sp_rt.cwd = dir; sp_sys_fd_t cwd = sp_fs_get_cwd(); - sp_path_t path = sp_path_at_root(sp_str_lit("A")); + sp_path_t path = sp_path_at_cwd(sp_str_lit("A")); bool found = sp_fs_is_file(sp_str_lit("A")); sp_str_t cwd_path = sp_fs_get_cwd_path(sp_test_arena(t)); sp_rt.cwd = SP_SYS_INVALID_FD; @@ -70,10 +70,10 @@ sp_test(fs, path_at_root_resolves_against_cwd) { sp_str_t file = sp_fs_join_path(sp_test_arena(t), sp_test_dir(t), sp_str_lit("A")); sp_must_ok(t, sp_fs_create_file(file)); - sp_path_t path = sp_path_at_root(file); + sp_path_t path = sp_path_at_cwd(file); sp_expect_eq(t, path.dir, sp_fs_get_cwd()); sp_expect_str_eq(t, path.sub, file); sp_expect(t, sp_fs_is_file_at(path)); - sp_expect(t, sp_fs_is_dir_at(sp_path_at_root(sp_str_lit(".")))); + sp_expect(t, sp_fs_is_dir_at(sp_path_at_cwd(sp_str_lit(".")))); return SP_OK; } diff --git a/test/fs/resolve.c b/test/fs/resolve.c index b19a4589..f50d3d19 100644 --- a/test/fs/resolve.c +++ b/test/fs/resolve.c @@ -39,9 +39,8 @@ static sp_sys_fd_t get_root(s32 it) { return (sp_sys_fd_t)(FD_BASE + it); } -static sp_err_t get_fd_path(sp_sys_fd_t fd, c8* buf, u64 size, u64* len) { +static sp_err_t get_root_label(s32 it, c8* buf, u64 size, u64* len) { *len = 0; - s32 it = (s32)fd - FD_BASE; if (it < 0 || it >= num_roots()) return SP_ERR_SYS_BAD_FD; const root_t* root = &active->roots[it]; @@ -151,11 +150,11 @@ static const test_t tests [] = { static sp_err_t run(sp_test_t* t, test_t* c) { sp_sys_vtable_t vt = sp_sys_vtable_platform; vt.get_root = get_root; - vt.get_fd_path = get_fd_path; + vt.get_root_label = get_root_label; active = c; const sp_sys_vtable_t* saved = sp_sys_set_vtable(&vt); - sp_path_t path = sp_path_resolve_for(sp_cstr_as_str(c->path), c->kind); + sp_path_t path = sp_path_resolve(sp_cstr_as_str(c->path)); sp_sys_set_vtable(saved); active = SP_NULLPTR; From 5e48e1f212387dfe84e3115f2084f047d2dce969 Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Sun, 27 Sep 2026 03:45:54 -0400 Subject: [PATCH 37/72] fs: tests for host kind helpers, root labels, fd paths --- spn.toml | 4 +- test/cli/complete.c | 16 +- test/fs/get_cwd.c | 2 +- test/fs/get_ext.c | 33 ++-- test/fs/get_name.c | 31 +++- test/fs/get_name_for.c | 23 --- test/fs/get_stem.c | 36 ++-- test/fs/host_kind.c | 58 ++++++ test/fs/is_absolute.c | 7 +- test/fs/is_root.c | 32 ++-- test/fs/is_sep.c | 23 +++ test/fs/join_path.c | 36 ++-- test/fs/normalize_path.c | 35 ++-- test/fs/parent_path.c | 37 ++-- test/fs/replace_ext.c | 25 ++- test/fs/resolve.c | 38 ++-- test/fs/trim_path.c | 35 ++-- test/sys/fd_path.c | 217 ++++++++++++++++++++++ test/sys/path.c | 49 ----- test/sys/root.c | 39 +++- test/sys/self_path.c | 6 +- tools/windows/sp/spn/amalgamation.vcxproj | 2 +- tools/windows/sp/spn/sys.vcxproj | 2 +- 23 files changed, 552 insertions(+), 234 deletions(-) delete mode 100644 test/fs/get_name_for.c create mode 100644 test/fs/host_kind.c create mode 100644 test/fs/is_sep.c create mode 100644 test/sys/fd_path.c diff --git a/spn.toml b/spn.toml index fabcd951..7aeacfc2 100644 --- a/spn.toml +++ b/spn.toml @@ -16,7 +16,7 @@ source = [ "test/test/axis.c", "test/test/filter.c", "test/test/matrix.c", "test/test/resolve.c", "test/test/sweep.c", "test/test/tracking.c", "test/sys/sys.c", "test/sys/open.c", "test/sys/path.c", "test/sys/futex.c", - "test/sys/fd.c", "test/sys/nanosleep.c", "test/sys/timestamps.c", "test/sys/perms.c", "test/sys/self_path.c", + "test/sys/fd.c", "test/sys/fd_path.c", "test/sys/nanosleep.c", "test/sys/timestamps.c", "test/sys/perms.c", "test/sys/self_path.c", "test/sys/pipe/data.c", "test/sys/pipe/blocking.c", "test/sys/transfer.c", "test/sys/readlink.c", "test/sys/socket/data.c", "test/sys/socket/listen.c", "test/sys/socket/desc.c", "test/sys/tty/contract.c", "test/sys/tty/mode.c", @@ -40,7 +40,7 @@ name = "sys" source = [ "test/main.c", "test/sys/sys.c", "test/sys/open.c", "test/sys/path.c", "test/sys/futex.c", - "test/sys/fd.c", "test/sys/nanosleep.c", "test/sys/timestamps.c", "test/sys/perms.c", "test/sys/self_path.c", + "test/sys/fd.c", "test/sys/fd_path.c", "test/sys/nanosleep.c", "test/sys/timestamps.c", "test/sys/perms.c", "test/sys/self_path.c", "test/sys/pipe/data.c", "test/sys/pipe/blocking.c", "test/sys/transfer.c", "test/sys/readlink.c", "test/sys/socket/data.c", "test/sys/socket/listen.c", "test/sys/socket/desc.c", "test/sys/tty/contract.c", "test/sys/tty/mode.c", diff --git a/test/cli/complete.c b/test/cli/complete.c index 55b8afa0..25491db1 100644 --- a/test/cli/complete.c +++ b/test/cli/complete.c @@ -408,12 +408,10 @@ static void run_cli_completer_path_test(s32* utest_result, sp_mem_t mem, cli_com }, buffer, sizeof(buffer)); if (t.expect.anchored) { - sp_sys_fd_t dir = sp_fs_get_cwd(); - if (dir == SP_SYS_INVALID_FD) UTEST_SKIP("no cwd"); - c8 cwd [SP_PATH_MAX]; u64 cwd_len = 0; - sp_err_t err = sp_sys_get_fd_path(dir, cwd, sizeof(cwd), &cwd_len); + sp_err_t err = sp_sys_get_fd_path(sp_fs_get_cwd(), cwd, sizeof(cwd), &cwd_len); + if (err == SP_ERR_SYS_UNSUPPORTED) UTEST_SKIP("no cwd path"); EXPECT_EQ(err, SP_OK); if (err) return; sp_str_t expected = sp_fmt(mem, "{}/{}", sp_fmt_str(sp_str(cwd, sp_cast(u32, cwd_len))), sp_fmt_cstr(t.arg0)).value; @@ -438,12 +436,22 @@ UTEST_F(cli_complete, completer_path_relative) { } UTEST_F(cli_complete, completer_path_relative_backslash) { + SKIP_UNLESS_WIN32() + run_cli_completer_path_test(&ur, ut.mem.arena, (cli_completer_path_test_t) { .arg0 = "B\\A", .expect = { .anchored = true }, }); } +UTEST_F(cli_complete, completer_path_backslash_is_a_name) { + SKIP_ON_WIN32() + + run_cli_completer_path_test(&ur, ut.mem.arena, (cli_completer_path_test_t) { + .arg0 = "B\\A", + }); +} + UTEST_F(cli_complete, completer_path_absolute) { run_cli_completer_path_test(&ur, ut.mem.arena, (cli_completer_path_test_t) { .arg0 = "/B/A", diff --git a/test/fs/get_cwd.c b/test/fs/get_cwd.c index 8bdadb9b..4653164e 100644 --- a/test/fs/get_cwd.c +++ b/test/fs/get_cwd.c @@ -64,7 +64,7 @@ sp_test(fs, get_cwd_override, .serial = true) { return SP_OK; } -sp_test(fs, path_at_root_resolves_against_cwd) { +sp_test(fs, path_at_cwd_passes_through) { sp_test_skip_on_wasm() sp_str_t file = sp_fs_join_path(sp_test_arena(t), sp_test_dir(t), sp_str_lit("A")); diff --git a/test/fs/get_ext.c b/test/fs/get_ext.c index fc90332b..db6b1376 100644 --- a/test/fs/get_ext.c +++ b/test/fs/get_ext.c @@ -3,26 +3,33 @@ typedef struct { const c8* name; + sp_fs_path_kind_t kind; const c8* input; const c8* expect; } test_t; static const test_t tests [] = { - { .name = "empty", .input = "", .expect = "" }, - { .name = "no_ext", .input = "A", .expect = "" }, - { .name = "no_ext_with_dir", .input = "A/B", .expect = "" }, - { .name = "ext_after_dir", .input = "A/B.txt", .expect = "txt" }, - { .name = "double_dot", .input = "A..txt", .expect = "txt" }, - { .name = "leading_dot", .input = ".A", .expect = "A" }, - { .name = "simple", .input = "A.B", .expect = "B" }, - { .name = "trailing_dot", .input = "A.", .expect = "" }, - { .name = "multiple_dots", .input = "A.B.C", .expect = "C" }, - { .name = "multiple_dots_trailing", .input = "A.B.", .expect = "" }, - { .name = "windows_path", .input = "C:\\A\\B.txt", .expect = "txt" }, - { .name = "dot_in_dir", .input = "/A/B.C/D", .expect = "" }, + { .name = "empty", .input = "", .expect = "" }, + { .name = "none", .input = "A", .expect = "" }, + { .name = "simple", .input = "A.B", .expect = "B" }, + { .name = "nested", .input = "A/B.txt", .expect = "txt" }, + { .name = "trailing_slash", .input = "A/B.txt/", .expect = "txt" }, + { .name = "last_dot_wins", .input = "A.B.C", .expect = "C" }, + { .name = "double_dot", .input = "A..txt", .expect = "txt" }, + { .name = "trailing_dot", .input = "A.", .expect = "" }, + { .name = "dot_in_dir", .input = "/A/B.C/D", .expect = "" }, + { .name = "hidden", .input = ".A", .expect = "" }, + { .name = "hidden_with_ext", .input = ".A.txt", .expect = "txt" }, + { .name = "dot", .input = ".", .expect = "" }, + { .name = "dotdot", .input = "..", .expect = "" }, + { .name = "root", .input = "/", .expect = "" }, + { .name = "backslash_is_a_name", .input = "A.B\\C", .expect = "B\\C" }, + + { .name = "windows_backslash", .kind = SP_FS_PATH_WINDOWS, .input = "C:\\A\\B.txt", .expect = "txt" }, + { .name = "windows_dot_in_dir", .kind = SP_FS_PATH_WINDOWS, .input = "A.B\\C", .expect = "" }, }; sp_test_each(fs, get_ext, test_t, tests) { - sp_expect_str_eq_c(t, sp_fs_get_ext(sp_str_view(it->input)), it->expect); + sp_expect_str_eq_c(t, sp_fs_get_ext_for(sp_cstr_as_str(it->input), it->kind), it->expect); return SP_OK; } diff --git a/test/fs/get_name.c b/test/fs/get_name.c index 5982f9ba..fbd5ce95 100644 --- a/test/fs/get_name.c +++ b/test/fs/get_name.c @@ -3,23 +3,34 @@ typedef struct { const c8* name; + sp_fs_path_kind_t kind; const c8* input; const c8* expect; } test_t; static const test_t tests [] = { - { .name = "empty", .input = "", .expect = "" }, - { .name = "bare", .input = "A", .expect = "A" }, - { .name = "trailing_slash", .input = "A/", .expect = "" }, - { .name = "after_dir", .input = "A/B.txt", .expect = "B.txt" }, - // backslash: get_name only splits on '/', the whole thing is the name - { .name = "backslash_not_separator", .input = "C:\\A\\B.txt", .expect = "C:\\A\\B.txt" }, - { .name = "drive_forward", .input = "C:/A/B/C.txt", .expect = "C.txt" }, - { .name = "absolute", .input = "/A", .expect = "A" }, - { .name = "absolute_trailing_slash", .input = "/A/B/", .expect = "" }, + { .name = "empty", .input = "", .expect = "" }, + { .name = "bare", .input = "A", .expect = "A" }, + { .name = "trailing_slash", .input = "A/", .expect = "A" }, + { .name = "nested", .input = "A/B.txt", .expect = "B.txt" }, + { .name = "nested_trailing", .input = "A/B/", .expect = "B" }, + { .name = "absolute", .input = "/A", .expect = "A" }, + { .name = "root", .input = "/", .expect = "" }, + { .name = "dot", .input = ".", .expect = "." }, + { .name = "dotdot", .input = "A/..", .expect = ".." }, + { .name = "backslash_is_a_name", .input = "A\\B", .expect = "A\\B" }, + { .name = "drive_is_a_name", .input = "C:/", .expect = "C:" }, + + { .name = "windows_backslash", .kind = SP_FS_PATH_WINDOWS, .input = "C:\\A\\B.txt", .expect = "B.txt" }, + { .name = "windows_mixed", .kind = SP_FS_PATH_WINDOWS, .input = "A\\B/C", .expect = "C" }, + { .name = "windows_trailing", .kind = SP_FS_PATH_WINDOWS, .input = "A\\B\\", .expect = "B" }, + { .name = "windows_dotdot", .kind = SP_FS_PATH_WINDOWS, .input = "A\\..", .expect = ".." }, + { .name = "windows_root", .kind = SP_FS_PATH_WINDOWS, .input = "\\", .expect = "" }, + { .name = "windows_drive", .kind = SP_FS_PATH_WINDOWS, .input = "C:\\", .expect = "" }, + { .name = "windows_under_drive", .kind = SP_FS_PATH_WINDOWS, .input = "C:/A", .expect = "A" }, }; sp_test_each(fs, get_name, test_t, tests) { - sp_expect_str_eq_c(t, sp_fs_get_name(sp_str_view(it->input)), it->expect); + sp_expect_str_eq_c(t, sp_fs_get_name_for(sp_cstr_as_str(it->input), it->kind), it->expect); return SP_OK; } diff --git a/test/fs/get_name_for.c b/test/fs/get_name_for.c deleted file mode 100644 index 5f5c1dc0..00000000 --- a/test/fs/get_name_for.c +++ /dev/null @@ -1,23 +0,0 @@ -#include "sp.h" -#include "sp/sp_test.h" - -typedef struct { - const c8* name; - sp_fs_path_kind_t kind; - const c8* input; - const c8* expect; -} test_t; - -static const test_t tests [] = { - { .name = "windows_backslash", .kind = SP_FS_PATH_WINDOWS, .input = "C:\\A\\B.txt", .expect = "B.txt" }, - { .name = "windows_mixed", .kind = SP_FS_PATH_WINDOWS, .input = "A\\B/C", .expect = "C" }, - { .name = "windows_dotdot", .kind = SP_FS_PATH_WINDOWS, .input = "A\\..", .expect = ".." }, - { .name = "windows_drive_root", .kind = SP_FS_PATH_WINDOWS, .input = "C:\\", .expect = "" }, - { .name = "posix_backslash", .kind = SP_FS_PATH_POSIX, .input = "A\\B", .expect = "A\\B" }, - { .name = "posix_slash", .kind = SP_FS_PATH_POSIX, .input = "A/B", .expect = "B" }, -}; - -sp_test_each(fs, get_name_for, test_t, tests) { - sp_expect_str_eq_c(t, sp_fs_get_name_for(sp_cstr_as_str(it->input), it->kind), it->expect); - return SP_OK; -} diff --git a/test/fs/get_stem.c b/test/fs/get_stem.c index 48d4770a..37683b34 100644 --- a/test/fs/get_stem.c +++ b/test/fs/get_stem.c @@ -3,30 +3,32 @@ typedef struct { const c8* name; + sp_fs_path_kind_t kind; const c8* input; const c8* expect; } test_t; static const test_t tests [] = { - { .name = "empty", .input = "", .expect = "" }, - { .name = "bare", .input = "A", .expect = "A" }, - { .name = "trailing_slash", .input = "A/", .expect = "A/" }, - { .name = "after_dir", .input = "A/B", .expect = "B" }, - { .name = "strips_ext", .input = "A/B.txt", .expect = "B" }, - { .name = "double_dot", .input = "A..txt", .expect = "A." }, - { .name = "leading_dot", .input = ".A", .expect = "" }, - { .name = "leading_dot_with_ext", .input = ".A.txt", .expect = ".A" }, - { .name = "drive_forward", .input = "C:/A/B.txt", .expect = "B" }, - // backslash: get_name returns the whole thing, then the extension is stripped - { .name = "backslash_not_separator", .input = "C:\\A\\B.txt", .expect = "C:\\A\\B" }, - { .name = "simple", .input = "A.B", .expect = "A" }, - { .name = "trailing_dot", .input = "A.", .expect = "A" }, - { .name = "multiple_dots", .input = "A.B.C", .expect = "A.B" }, - { .name = "multiple_dots_trailing", .input = "A.B.", .expect = "A.B" }, - { .name = "hidden_single", .input = ".B", .expect = "" }, + { .name = "empty", .input = "", .expect = "" }, + { .name = "none", .input = "A", .expect = "A" }, + { .name = "simple", .input = "A.B", .expect = "A" }, + { .name = "nested", .input = "A/B.txt", .expect = "B" }, + { .name = "trailing_slash", .input = "A/B.txt/", .expect = "B" }, + { .name = "last_dot_wins", .input = "A.B.C", .expect = "A.B" }, + { .name = "double_dot", .input = "A..txt", .expect = "A." }, + { .name = "trailing_dot", .input = "A.", .expect = "A" }, + { .name = "hidden", .input = ".A", .expect = ".A" }, + { .name = "hidden_with_ext", .input = ".A.txt", .expect = ".A" }, + { .name = "dot", .input = ".", .expect = "." }, + { .name = "dotdot", .input = "..", .expect = ".." }, + { .name = "root", .input = "/", .expect = "" }, + { .name = "backslash_is_a_name", .input = "A\\B.txt", .expect = "A\\B" }, + + { .name = "windows_backslash", .kind = SP_FS_PATH_WINDOWS, .input = "C:\\A\\B.txt", .expect = "B" }, + { .name = "windows_dot_in_dir", .kind = SP_FS_PATH_WINDOWS, .input = "A.B\\C", .expect = "C" }, }; sp_test_each(fs, get_stem, test_t, tests) { - sp_expect_str_eq_c(t, sp_fs_get_stem(sp_str_view(it->input)), it->expect); + sp_expect_str_eq_c(t, sp_fs_get_stem_for(sp_cstr_as_str(it->input), it->kind), it->expect); return SP_OK; } diff --git a/test/fs/host_kind.c b/test/fs/host_kind.c new file mode 100644 index 00000000..e4abf384 --- /dev/null +++ b/test/fs/host_kind.c @@ -0,0 +1,58 @@ +#include "sp.h" +#include "sp/sp_test.h" + +typedef enum { + FN_IS_SEP, + FN_IS_ROOT, + FN_IS_ABSOLUTE, + FN_TRIM_PATH, + FN_GET_NAME, + FN_PARENT_PATH, + FN_GET_EXT, + FN_GET_STEM, + FN_REPLACE_EXT, + FN_JOIN_PATH, + FN_NORMALIZE_PATH, +} fn_t; + +typedef struct { + const c8* name; + fn_t fn; + const c8* input; +} test_t; + +static const test_t tests [] = { + { .name = "is_sep", .fn = FN_IS_SEP, .input = "\\" }, + { .name = "is_root", .fn = FN_IS_ROOT, .input = "C:\\" }, + { .name = "is_absolute", .fn = FN_IS_ABSOLUTE, .input = "\\A" }, + { .name = "trim_path", .fn = FN_TRIM_PATH, .input = "A\\" }, + { .name = "get_name", .fn = FN_GET_NAME, .input = "A\\B" }, + { .name = "parent_path", .fn = FN_PARENT_PATH, .input = "A\\B" }, + { .name = "get_ext", .fn = FN_GET_EXT, .input = "A.B\\C" }, + { .name = "get_stem", .fn = FN_GET_STEM, .input = "A.B\\C" }, + { .name = "replace_ext", .fn = FN_REPLACE_EXT, .input = "A.B\\C" }, + { .name = "join_path", .fn = FN_JOIN_PATH, .input = "A\\" }, + { .name = "normalize_path", .fn = FN_NORMALIZE_PATH, .input = "A\\B" }, +}; + +sp_test_each(fs, host_kind, test_t, tests) { + sp_mem_t mem = sp_test_arena(t); + sp_fs_path_kind_t kind = sp_os_get_path_kind(); + sp_str_t path = sp_cstr_as_str(it->input); + sp_str_t other = sp_str_lit("D"); + + switch (it->fn) { + case FN_IS_SEP: sp_expect_eq(t, sp_fs_is_sep(path.data[0]), sp_fs_is_sep_for(path.data[0], kind)); break; + case FN_IS_ROOT: sp_expect_eq(t, sp_fs_is_root(path), sp_fs_is_root_for(path, kind)); break; + case FN_IS_ABSOLUTE: sp_expect_eq(t, sp_fs_is_absolute(path), sp_fs_is_absolute_for(path, kind)); break; + case FN_TRIM_PATH: sp_expect_str_eq(t, sp_fs_trim_path(path), sp_fs_trim_path_for(path, kind)); break; + case FN_GET_NAME: sp_expect_str_eq(t, sp_fs_get_name(path), sp_fs_get_name_for(path, kind)); break; + case FN_PARENT_PATH: sp_expect_str_eq(t, sp_fs_parent_path(path), sp_fs_parent_path_for(path, kind)); break; + case FN_GET_EXT: sp_expect_str_eq(t, sp_fs_get_ext(path), sp_fs_get_ext_for(path, kind)); break; + case FN_GET_STEM: sp_expect_str_eq(t, sp_fs_get_stem(path), sp_fs_get_stem_for(path, kind)); break; + case FN_REPLACE_EXT: sp_expect_str_eq(t, sp_fs_replace_ext(mem, path, other), sp_fs_replace_ext_for(mem, path, other, kind)); break; + case FN_JOIN_PATH: sp_expect_str_eq(t, sp_fs_join_path(mem, path, other), sp_fs_join_path_for(mem, path, other, kind)); break; + case FN_NORMALIZE_PATH: sp_expect_str_eq(t, sp_fs_normalize_path(mem, path), sp_fs_normalize_path_for(mem, path, kind)); break; + } + return SP_OK; +} diff --git a/test/fs/is_absolute.c b/test/fs/is_absolute.c index 438d83df..1fbdd3b3 100644 --- a/test/fs/is_absolute.c +++ b/test/fs/is_absolute.c @@ -11,12 +11,13 @@ typedef struct { static const test_t tests [] = { { .name = "empty", .input = "" }, { .name = "root_slash", .input = "/", .posix = true, .windows = true }, - { .name = "root_backslash", .input = "\\", .posix = true, .windows = true }, + { .name = "root_backslash", .input = "\\", .windows = true }, { .name = "double_slash", .input = "//", .posix = true, .windows = true }, { .name = "slash_prefix", .input = "/A", .posix = true, .windows = true }, - { .name = "backslash_prefix", .input = "\\A", .posix = true, .windows = true }, + { .name = "backslash_prefix", .input = "\\A", .windows = true }, { .name = "relative", .input = "A" }, { .name = "relative_nested", .input = "A/B" }, + { .name = "dot", .input = "." }, { .name = "drive_bare", .input = "C:" }, { .name = "drive_lower_bare", .input = "a:" }, { .name = "drive_relative", .input = "C:A" }, @@ -27,7 +28,7 @@ static const test_t tests [] = { }; sp_test_each(fs, is_absolute, test_t, tests) { - sp_str_t path = sp_str_view(it->input); + sp_str_t path = sp_cstr_as_str(it->input); sp_expect_eq(t, sp_fs_is_absolute_for(path, SP_FS_PATH_POSIX), it->posix); sp_expect_eq(t, sp_fs_is_absolute_for(path, SP_FS_PATH_WINDOWS), it->windows); return SP_OK; diff --git a/test/fs/is_root.c b/test/fs/is_root.c index bdff5004..93a07ae6 100644 --- a/test/fs/is_root.c +++ b/test/fs/is_root.c @@ -4,24 +4,30 @@ typedef struct { const c8* name; const c8* input; - bool root; + bool posix; + bool windows; } test_t; static const test_t tests [] = { - { .name = "empty", .input = "" }, - { .name = "slash", .input = "/", .root = true }, - { .name = "backslash", .input = "\\", .root = true }, - { .name = "drive_bare", .input = "C:" }, - { .name = "drive_lower_bare", .input = "a:" }, - { .name = "drive_slash", .input = "C:/", .root = true }, - { .name = "drive_backslash", .input = "C:\\", .root = true }, - { .name = "relative", .input = "A" }, - { .name = "slash_prefix", .input = "/A" }, - { .name = "drive_prefix", .input = "C:/A" }, - { .name = "double_slash", .input = "//" }, + { .name = "empty", .input = "" }, + { .name = "slash", .input = "/", .posix = true, .windows = true }, + { .name = "slash_run", .input = "//", .posix = true, .windows = true }, + { .name = "backslash", .input = "\\", .windows = true }, + { .name = "drive_bare", .input = "C:" }, + { .name = "drive_lower_bare", .input = "a:" }, + { .name = "drive_slash", .input = "C:/", .windows = true }, + { .name = "drive_slash_run", .input = "C://", .windows = true }, + { .name = "drive_backslash", .input = "C:\\", .windows = true }, + { .name = "relative", .input = "A" }, + { .name = "dot", .input = "." }, + { .name = "slash_prefix", .input = "/A" }, + { .name = "slash_prefix_trailing", .input = "/A/" }, + { .name = "drive_prefix", .input = "C:/A" }, }; sp_test_each(fs, is_root, test_t, tests) { - sp_expect_eq(t, sp_fs_is_root(sp_str_view(it->input)), it->root); + sp_str_t path = sp_cstr_as_str(it->input); + sp_expect_eq(t, sp_fs_is_root_for(path, SP_FS_PATH_POSIX), it->posix); + sp_expect_eq(t, sp_fs_is_root_for(path, SP_FS_PATH_WINDOWS), it->windows); return SP_OK; } diff --git a/test/fs/is_sep.c b/test/fs/is_sep.c new file mode 100644 index 00000000..d6580b5b --- /dev/null +++ b/test/fs/is_sep.c @@ -0,0 +1,23 @@ +#include "sp.h" +#include "sp/sp_test.h" + +typedef struct { + const c8* name; + c8 input; + bool posix; + bool windows; +} test_t; + +static const test_t tests [] = { + { .name = "slash", .input = '/', .posix = true, .windows = true }, + { .name = "backslash", .input = '\\', .windows = true }, + { .name = "letter", .input = 'A' }, + { .name = "colon", .input = ':' }, + { .name = "dot", .input = '.' }, +}; + +sp_test_each(fs, is_sep, test_t, tests) { + sp_expect_eq(t, sp_fs_is_sep_for(it->input, SP_FS_PATH_POSIX), it->posix); + sp_expect_eq(t, sp_fs_is_sep_for(it->input, SP_FS_PATH_WINDOWS), it->windows); + return SP_OK; +} diff --git a/test/fs/join_path.c b/test/fs/join_path.c index 60b41328..e4bda650 100644 --- a/test/fs/join_path.c +++ b/test/fs/join_path.c @@ -3,27 +3,39 @@ typedef struct { const c8* name; + sp_fs_path_kind_t kind; const c8* a; const c8* b; const c8* expect; } test_t; static const test_t tests [] = { - { .name = "basic", .a = "A", .b = "B", .expect = "A/B" }, - { .name = "trailing_slash_lhs", .a = "A/", .b = "B", .expect = "A/B" }, - { .name = "trailing_slash_both", .a = "A/", .b = "B/", .expect = "A/B" }, - { .name = "nested_rhs", .a = "A", .b = "B/C", .expect = "A/B/C" }, - { .name = "root_lhs", .a = "/", .b = "A", .expect = "/A" }, - { .name = "root_lhs_nested", .a = "/", .b = "A/B", .expect = "/A/B" }, - { .name = "drive_slash_lhs", .a = "C:/", .b = "A", .expect = "C:/A" }, - { .name = "drive_bare_lhs", .a = "C:", .b = "A", .expect = "C:/A" }, - { .name = "empty_lhs", .a = "", .b = "A", .expect = "A" }, - { .name = "empty_rhs", .a = "A", .b = "", .expect = "A" }, - { .name = "empty_both", .a = "", .b = "", .expect = "" }, + { .name = "basic", .a = "A", .b = "B", .expect = "A/B" }, + { .name = "nested_rhs", .a = "A", .b = "B/C", .expect = "A/B/C" }, + { .name = "trailing_lhs", .a = "A/", .b = "B", .expect = "A/B" }, + { .name = "trailing_rhs", .a = "A", .b = "B/", .expect = "A/B" }, + { .name = "leading_rhs", .a = "A", .b = "/B", .expect = "A/B" }, + { .name = "leading_run_rhs", .a = "A/", .b = "//B", .expect = "A/B" }, + { .name = "absolute_both", .a = "/A", .b = "/B", .expect = "/A/B" }, + { .name = "root_lhs", .a = "/", .b = "A", .expect = "/A" }, + { .name = "root_both", .a = "/", .b = "/A", .expect = "/A" }, + { .name = "root_rhs", .a = "A", .b = "/", .expect = "A" }, + { .name = "dot_lhs", .a = ".", .b = "A", .expect = "./A" }, + { .name = "empty_lhs", .a = "", .b = "A", .expect = "A" }, + { .name = "empty_lhs_absolute", .a = "", .b = "/A", .expect = "/A" }, + { .name = "empty_rhs", .a = "A", .b = "", .expect = "A" }, + { .name = "empty_both", .a = "", .b = "", .expect = "" }, + { .name = "backslash_is_a_name", .a = "A\\", .b = "\\B", .expect = "A\\/\\B" }, + + { .name = "windows_trailing_lhs", .kind = SP_FS_PATH_WINDOWS, .a = "A\\", .b = "B", .expect = "A/B" }, + { .name = "windows_leading_rhs", .kind = SP_FS_PATH_WINDOWS, .a = "A", .b = "\\B", .expect = "A/B" }, + { .name = "windows_drive", .kind = SP_FS_PATH_WINDOWS, .a = "C:/", .b = "A", .expect = "C:/A" }, + { .name = "windows_drive_back", .kind = SP_FS_PATH_WINDOWS, .a = "C:\\", .b = "A", .expect = "C:\\A" }, + { .name = "windows_drive_bare", .kind = SP_FS_PATH_WINDOWS, .a = "C:", .b = "A", .expect = "C:/A" }, }; sp_test_each(fs, join_path, test_t, tests) { - sp_str_t result = sp_fs_join_path(sp_test_arena(t), sp_str_view(it->a), sp_str_view(it->b)); + sp_str_t result = sp_fs_join_path_for(sp_test_arena(t), sp_cstr_as_str(it->a), sp_cstr_as_str(it->b), it->kind); sp_expect_str_eq_c(t, result, it->expect); return SP_OK; } diff --git a/test/fs/normalize_path.c b/test/fs/normalize_path.c index fa25644b..711acf05 100644 --- a/test/fs/normalize_path.c +++ b/test/fs/normalize_path.c @@ -3,32 +3,31 @@ typedef struct { const c8* name; + sp_fs_path_kind_t kind; const c8* input; const c8* expect; } test_t; static const test_t tests [] = { - { .name = "empty", .input = "", .expect = "" }, - { .name = "bare", .input = "A", .expect = "A" }, - { .name = "trailing_slash", .input = "A/", .expect = "A" }, - { .name = "nested", .input = "A/B", .expect = "A/B" }, - { .name = "nested_ext", .input = "A/B.txt", .expect = "A/B.txt" }, - { .name = "hidden", .input = ".A", .expect = ".A" }, - { .name = "drive_forward", .input = "C:/A/B.txt", .expect = "C:/A/B.txt" }, - { .name = "drive_backslash", .input = "C:\\A\\B.txt", .expect = "C:/A/B.txt" }, - { .name = "mixed_separators", .input = "C:/A\\B/C\\D.txt", .expect = "C:/A/B/C/D.txt" }, - { .name = "drive_backslash_trailing", .input = "C:\\A\\B\\", .expect = "C:/A/B" }, - { .name = "backslash", .input = "A\\B", .expect = "A/B" }, - { .name = "root_backslash", .input = "\\", .expect = "/" }, - { .name = "root_slash", .input = "/", .expect = "/" }, - { .name = "backslash_trailing", .input = "A\\", .expect = "A" }, - { .name = "preserves_dotdot", .input = "A\\B\\..\\C", .expect = "A/B/../C" }, - { .name = "preserves_dot", .input = "A\\.\\B", .expect = "A/./B" }, - { .name = "nonexistent_path", .input = "C:\\no\\such\\path\\file.txt", .expect = "C:/no/such/path/file.txt" }, + { .name = "empty", .input = "", .expect = "" }, + { .name = "bare", .input = "A", .expect = "A" }, + { .name = "nested", .input = "A/B", .expect = "A/B" }, + { .name = "trailing_slash", .input = "A/", .expect = "A" }, + { .name = "root", .input = "/", .expect = "/" }, + { .name = "keeps_dot", .input = "A/./B", .expect = "A/./B" }, + { .name = "keeps_dotdot", .input = "A/B/../C", .expect = "A/B/../C" }, + { .name = "backslash_is_a_name", .input = "A\\B\\", .expect = "A\\B\\" }, + + { .name = "windows_backslash", .kind = SP_FS_PATH_WINDOWS, .input = "A\\B", .expect = "A/B" }, + { .name = "windows_mixed", .kind = SP_FS_PATH_WINDOWS, .input = "C:/A\\B/C\\D.txt", .expect = "C:/A/B/C/D.txt" }, + { .name = "windows_trailing", .kind = SP_FS_PATH_WINDOWS, .input = "C:\\A\\B\\", .expect = "C:/A/B" }, + { .name = "windows_root", .kind = SP_FS_PATH_WINDOWS, .input = "\\", .expect = "/" }, + { .name = "windows_drive", .kind = SP_FS_PATH_WINDOWS, .input = "C:\\", .expect = "C:/" }, + { .name = "windows_keeps_dotdot", .kind = SP_FS_PATH_WINDOWS, .input = "A\\B\\..\\C", .expect = "A/B/../C" }, }; sp_test_each(fs, normalize_path, test_t, tests) { - sp_str_t result = sp_fs_normalize_path(sp_test_arena(t), sp_str_view(it->input)); + sp_str_t result = sp_fs_normalize_path_for(sp_test_arena(t), sp_cstr_as_str(it->input), it->kind); sp_expect_str_eq_c(t, result, it->expect); return SP_OK; } diff --git a/test/fs/parent_path.c b/test/fs/parent_path.c index aa429034..46513d7e 100644 --- a/test/fs/parent_path.c +++ b/test/fs/parent_path.c @@ -3,28 +3,35 @@ typedef struct { const c8* name; + sp_fs_path_kind_t kind; const c8* input; const c8* expect; } test_t; static const test_t tests [] = { - { .name = "empty", .input = "", .expect = "" }, - { .name = "bare", .input = "A", .expect = "" }, - { .name = "trailing_slash", .input = "A/", .expect = "" }, - { .name = "nested", .input = "A/B", .expect = "A" }, - { .name = "nested_ext", .input = "A/B.txt", .expect = "A" }, - // backslash: no '/' found so the parent is empty - { .name = "backslash_not_separator", .input = "C:\\A\\B.txt", .expect = "" }, - { .name = "drive_nested", .input = "C:/A/B/C.txt", .expect = "C:/A/B" }, - { .name = "drive_trailing_slash", .input = "C:/A/B/", .expect = "C:/A" }, - { .name = "drive_multi_trailing", .input = "C:/A/B///", .expect = "C:/A" }, - { .name = "drive_root", .input = "C:/", .expect = "C:/" }, - { .name = "root", .input = "/", .expect = "/" }, - { .name = "absolute_single", .input = "/A", .expect = "" }, - { .name = "absolute_nested", .input = "/A/B/C", .expect = "/A/B" }, + { .name = "empty", .input = "", .expect = "" }, + { .name = "bare", .input = "A", .expect = "." }, + { .name = "bare_trailing", .input = "A/", .expect = "." }, + { .name = "dot", .input = ".", .expect = "." }, + { .name = "nested", .input = "A/B", .expect = "A" }, + { .name = "nested_trailing", .input = "A/B/", .expect = "A" }, + { .name = "separator_run", .input = "A//B", .expect = "A" }, + { .name = "root", .input = "/", .expect = "/" }, + { .name = "under_root", .input = "/A", .expect = "/" }, + { .name = "absolute_nested", .input = "/A/B/C", .expect = "/A/B" }, + { .name = "backslash_is_a_name", .input = "A\\B", .expect = "." }, + { .name = "drive_is_a_name", .input = "C:/A", .expect = "C:" }, + + { .name = "windows_backslash", .kind = SP_FS_PATH_WINDOWS, .input = "A\\B", .expect = "A" }, + { .name = "windows_mixed", .kind = SP_FS_PATH_WINDOWS, .input = "A/B\\C", .expect = "A/B" }, + { .name = "windows_root", .kind = SP_FS_PATH_WINDOWS, .input = "\\", .expect = "\\" }, + { .name = "windows_under_root", .kind = SP_FS_PATH_WINDOWS, .input = "\\A", .expect = "\\" }, + { .name = "windows_drive", .kind = SP_FS_PATH_WINDOWS, .input = "C:/", .expect = "C:/" }, + { .name = "windows_under_drive", .kind = SP_FS_PATH_WINDOWS, .input = "C:\\A", .expect = "C:\\" }, + { .name = "windows_drive_nested", .kind = SP_FS_PATH_WINDOWS, .input = "C:/A/B///", .expect = "C:/A" }, }; sp_test_each(fs, parent_path, test_t, tests) { - sp_expect_str_eq_c(t, sp_fs_parent_path(sp_str_view(it->input)), it->expect); + sp_expect_str_eq_c(t, sp_fs_parent_path_for(sp_cstr_as_str(it->input), it->kind), it->expect); return SP_OK; } diff --git a/test/fs/replace_ext.c b/test/fs/replace_ext.c index e9776e53..88babdf3 100644 --- a/test/fs/replace_ext.c +++ b/test/fs/replace_ext.c @@ -3,24 +3,31 @@ typedef struct { const c8* name; + sp_fs_path_kind_t kind; const c8* input; const c8* ext; const c8* expect; } test_t; static const test_t tests [] = { - { .name = "swap", .input = "A.c", .ext = "o", .expect = "A.o" }, - { .name = "strip", .input = "A.c", .ext = "", .expect = "A" }, - { .name = "add", .input = "A", .ext = "txt", .expect = "A.txt" }, - { .name = "trailing_dot", .input = "A.", .ext = "txt", .expect = "A.txt" }, - { .name = "double_dot", .input = "A..txt", .ext = "md", .expect = "A..md" }, - { .name = "multiple_dots", .input = "A.B.C", .ext = "d", .expect = "A.B.d" }, - { .name = "hidden", .input = ".A", .ext = "txt", .expect = ".txt" }, - { .name = "nested", .input = "A/B.txt", .ext = "md", .expect = "A/B.md" }, + { .name = "swap", .input = "A.c", .ext = "o", .expect = "A.o" }, + { .name = "strip", .input = "A.c", .ext = "", .expect = "A" }, + { .name = "add", .input = "A", .ext = "txt", .expect = "A.txt" }, + { .name = "trailing_dot", .input = "A.", .ext = "txt", .expect = "A.txt" }, + { .name = "double_dot", .input = "A..txt", .ext = "md", .expect = "A..md" }, + { .name = "last_dot_wins", .input = "A.B.C", .ext = "d", .expect = "A.B.d" }, + { .name = "hidden", .input = ".A", .ext = "txt", .expect = ".A.txt" }, + { .name = "nested", .input = "A/B.txt", .ext = "md", .expect = "A/B.md" }, + { .name = "trailing_slash", .input = "A/B.txt/", .ext = "md", .expect = "A/B.md" }, + { .name = "dot_in_dir", .input = "A.B/C", .ext = "d", .expect = "A.B/C.d" }, + { .name = "backslash_is_a_name", .input = "A.B\\C", .ext = "d", .expect = "A.d" }, + + { .name = "windows_dot_in_dir", .kind = SP_FS_PATH_WINDOWS, .input = "A.B\\C", .ext = "d", .expect = "A.B\\C.d" }, + { .name = "windows_trailing", .kind = SP_FS_PATH_WINDOWS, .input = "A\\B.c\\", .ext = "o", .expect = "A\\B.o" }, }; sp_test_each(fs, replace_ext, test_t, tests) { - sp_str_t result = sp_fs_replace_ext(sp_test_arena(t), sp_str_view(it->input), sp_str_view(it->ext)); + sp_str_t result = sp_fs_replace_ext_for(sp_test_arena(t), sp_cstr_as_str(it->input), sp_cstr_as_str(it->ext), it->kind); sp_expect_str_eq_c(t, result, it->expect); return SP_OK; } diff --git a/test/fs/resolve.c b/test/fs/resolve.c index f50d3d19..4bf9989b 100644 --- a/test/fs/resolve.c +++ b/test/fs/resolve.c @@ -17,7 +17,6 @@ typedef struct { typedef struct { const c8* name; - sp_fs_path_kind_t kind; root_t roots [MAX_ROOTS]; const c8* path; expect_t expect; @@ -67,6 +66,12 @@ static const test_t tests [] = { .path = "/A/B", .expect = { .root = 1, .sub = "B" }, }, + { + .name = "separator_run_is_stripped", + .roots = { { .label = "/B" }, { .label = "/A" } }, + .path = "/A//B", + .expect = { .root = 1, .sub = "B" }, + }, { .name = "exact_label_is_dot", .roots = { { .label = "/B" }, { .label = "/A" } }, @@ -92,22 +97,28 @@ static const test_t tests [] = { .expect = { .sub = "/AB/C" }, }, { - .name = "longest_label_wins", + .name = "deepest_label_wins", .roots = { { .label = "/A" }, { .label = "/A/B" } }, .path = "/A/B/C", .expect = { .root = 1, .sub = "C" }, }, { - .name = "longest_label_wins_in_any_order", + .name = "deepest_label_wins_in_any_order", .roots = { { .label = "/A/B" }, { .label = "/A" } }, .path = "/A/B/C", .expect = { .sub = "C" }, }, { .name = "slash_label_takes_everything", - .roots = { { .label = "/" } }, + .roots = { { .label = "/B" }, { .label = "/" } }, .path = "/A/B", - .expect = { .sub = "A/B" }, + .expect = { .root = 1, .sub = "A/B" }, + }, + { + .name = "empty_label_takes_nothing", + .roots = { { .label = "" } }, + .path = "/A/B", + .expect = { .sub = "/A/B" }, }, { .name = "relative_label_never_matches", @@ -122,8 +133,8 @@ static const test_t tests [] = { .expect = { .sub = "/C" }, }, { - .name = "unreadable_root_is_skipped", - .roots = { { .err = SP_ERR_SYS_BAD_FD }, { .label = "/A" } }, + .name = "unreadable_label_is_skipped", + .roots = { { .err = SP_ERR_SYS_NAME_TOO_LONG }, { .label = "/A" } }, .path = "/A/B", .expect = { .root = 1, .sub = "B" }, }, @@ -132,19 +143,6 @@ static const test_t tests [] = { .path = "A", .expect = { .invalid = true, .sub = "A" }, }, - { - .name = "backslash_is_a_name_byte_on_posix", - .roots = { { .label = "/B" }, { .label = "/A" } }, - .path = "/A\\B", - .expect = { .sub = "/A\\B" }, - }, - { - .name = "backslash_separates_on_windows", - .kind = SP_FS_PATH_WINDOWS, - .roots = { { .label = "C:/B" }, { .label = "C:/A" } }, - .path = "C:/A\\B", - .expect = { .root = 1, .sub = "B" }, - }, }; static sp_err_t run(sp_test_t* t, test_t* c) { diff --git a/test/fs/trim_path.c b/test/fs/trim_path.c index f4d7b736..a25ca7ce 100644 --- a/test/fs/trim_path.c +++ b/test/fs/trim_path.c @@ -3,27 +3,34 @@ typedef struct { const c8* name; + sp_fs_path_kind_t kind; const c8* input; const c8* expect; } test_t; static const test_t tests [] = { - { .name = "empty", .input = "", .expect = "" }, - { .name = "bare", .input = "A", .expect = "A" }, - { .name = "trailing_slash", .input = "A/", .expect = "A" }, - { .name = "nested", .input = "A/B", .expect = "A/B" }, - { .name = "hidden", .input = ".A", .expect = ".A" }, - { .name = "drive_forward", .input = "C:/A/B.txt", .expect = "C:/A/B.txt" }, - { .name = "drive_backslash", .input = "C:\\A\\B.txt", .expect = "C:\\A\\B.txt" }, - { .name = "root_slash", .input = "/", .expect = "/" }, - { .name = "drive_slash", .input = "C:/", .expect = "C:/" }, - { .name = "drive_backslash_root", .input = "C:\\", .expect = "C:\\" }, - { .name = "absolute_trailing", .input = "/A/", .expect = "/A" }, - { .name = "multi_slash", .input = "A///", .expect = "A" }, - { .name = "multi_backslash", .input = "A\\\\", .expect = "A" }, + { .name = "empty", .input = "", .expect = "" }, + { .name = "bare", .input = "A", .expect = "A" }, + { .name = "trailing_slash", .input = "A/", .expect = "A" }, + { .name = "trailing_slash_run", .input = "A///", .expect = "A" }, + { .name = "nested", .input = "A/B", .expect = "A/B" }, + { .name = "absolute_trailing", .input = "/A/", .expect = "/A" }, + { .name = "root", .input = "/", .expect = "/" }, + { .name = "root_run", .input = "//", .expect = "/" }, + { .name = "backslash_is_a_name", .input = "A\\", .expect = "A\\" }, + { .name = "drive_is_a_name", .input = "C:/", .expect = "C:" }, + + { .name = "windows_slash", .kind = SP_FS_PATH_WINDOWS, .input = "A/", .expect = "A" }, + { .name = "windows_backslash", .kind = SP_FS_PATH_WINDOWS, .input = "A\\", .expect = "A" }, + { .name = "windows_mixed_run", .kind = SP_FS_PATH_WINDOWS, .input = "A/\\/", .expect = "A" }, + { .name = "windows_root", .kind = SP_FS_PATH_WINDOWS, .input = "\\", .expect = "\\" }, + { .name = "windows_drive", .kind = SP_FS_PATH_WINDOWS, .input = "C:/", .expect = "C:/" }, + { .name = "windows_drive_back", .kind = SP_FS_PATH_WINDOWS, .input = "C:\\", .expect = "C:\\" }, + { .name = "windows_drive_run", .kind = SP_FS_PATH_WINDOWS, .input = "C:\\\\", .expect = "C:\\" }, + { .name = "windows_drive_nested", .kind = SP_FS_PATH_WINDOWS, .input = "C:\\A\\", .expect = "C:\\A" }, }; sp_test_each(fs, trim_path, test_t, tests) { - sp_expect_str_eq_c(t, sp_fs_trim_path(sp_str_view(it->input)), it->expect); + sp_expect_str_eq_c(t, sp_fs_trim_path_for(sp_cstr_as_str(it->input), it->kind), it->expect); return SP_OK; } diff --git a/test/sys/fd_path.c b/test/sys/fd_path.c new file mode 100644 index 00000000..9aa46cb9 --- /dev/null +++ b/test/sys/fd_path.c @@ -0,0 +1,217 @@ +#include "sp.h" +#include "sp/sp_test.h" + +typedef enum { + HANDLE_FILE, + HANDLE_DIR, + HANDLE_CWD, + HANDLE_PIPE, + HANDLE_GARBAGE, +} handle_t; + +typedef enum { + FATE_KEPT, + FATE_REMOVED, +} fate_t; + +typedef struct { + const c8* name; + handle_t handle; + fate_t fate; + sp_err_t err; +} test_t; + +static const test_t tests [] = { + { .name = "file_is_named", .handle = HANDLE_FILE }, + { .name = "dir_is_named", .handle = HANDLE_DIR }, + { .name = "cwd_is_named", .handle = HANDLE_CWD }, + { .name = "removed_file_has_no_name", .handle = HANDLE_FILE, .fate = FATE_REMOVED, .err = SP_ERR_SYS_NOT_FOUND }, + { .name = "removed_dir_has_no_name", .handle = HANDLE_DIR, .fate = FATE_REMOVED, .err = SP_ERR_SYS_NOT_FOUND }, + { .name = "pipe_has_no_name", .handle = HANDLE_PIPE, .err = SP_ERR_SYS_NOT_FOUND }, + { .name = "garbage_is_not_a_handle", .handle = HANDLE_GARBAGE, .err = SP_ERR_SYS_BAD_FD }, +}; + +#define GARBAGE_FD ((sp_sys_fd_t)0x55555) + +static bool same_file(sp_path_t a, sp_path_t b) { + sp_sys_file_meta_t ma = sp_zero; + sp_sys_file_meta_t mb = sp_zero; + if (sp_sys_get_link_metadata_s(a.dir, a.sub, &ma)) return false; + if (sp_sys_get_link_metadata_s(b.dir, b.sub, &mb)) return false; + return ma.id == mb.id && ma.device == mb.device; +} + +static sp_err_t run(sp_test_t* t, test_t* c) { + sp_test_skip_on_wasm() + + sp_sys_fd_t sandbox = SP_SYS_INVALID_FD; + sp_must_ok(t, sp_sys_open_dir_s(sp_fs_get_cwd(), sp_test_dir(t), 0, &sandbox)); + + sp_path_t witness = { .dir = sandbox, .sub = sp_str_lit("A") }; + sp_sys_pipe_t pipe = sp_zero; + sp_sys_fd_t fd = SP_SYS_INVALID_FD; + switch (c->handle) { + case HANDLE_FILE: { + sp_must_ok(t, sp_fs_create_file_at(witness)); + sp_must_ok(t, sp_sys_open_s(witness.dir, witness.sub, SP_SYS_OPEN_MODE_RO, 0, &fd)); + break; + } + case HANDLE_DIR: { + sp_must_ok(t, sp_fs_create_dir_at(witness)); + sp_must_ok(t, sp_sys_open_dir_s(witness.dir, witness.sub, 0, &fd)); + break; + } + case HANDLE_CWD: { + witness = sp_path_at_cwd(sp_str_lit(".")); + fd = witness.dir; + break; + } + case HANDLE_PIPE: { + sp_must_ok(t, sp_sys_pipe(&pipe, sp_zero_s(sp_sys_pipe_desc_t))); + fd = pipe.r; + break; + } + case HANDLE_GARBAGE: { + fd = GARBAGE_FD; + break; + } + } + + switch (c->fate) { + case FATE_KEPT: { + break; + } + case FATE_REMOVED: { + sp_err_t removed = c->handle == HANDLE_DIR ? + sp_sys_rmdir_s(witness.dir, witness.sub) : + sp_sys_unlink_s(witness.dir, witness.sub); + sp_must_ok(t, removed); + break; + } + } + + c8 buf [SP_PATH_MAX]; + sp_for(it, sizeof(buf)) buf[it] = (c8)0xAB; + u64 len = 0; + sp_err_t err = sp_sys_get_fd_path(fd, buf, sizeof(buf), &len); + + sp_expect_err_eq(t, err, c->err); + if (!err) { + sp_expect_eq(t, buf[len], 0); + sp_expect(t, same_file(witness, sp_path_at_cwd(sp_str(buf, (u32)len)))); + } + + switch (c->handle) { + case HANDLE_FILE: + case HANDLE_DIR: { + sp_sys_close(fd); + break; + } + case HANDLE_PIPE: { + sp_sys_close(pipe.r); + sp_sys_close(pipe.w); + break; + } + case HANDLE_CWD: + case HANDLE_GARBAGE: { + break; + } + } + sp_sys_close(sandbox); + return SP_OK; +} + +sp_test_each_fn(sys, fd_path, test_t, tests, run); + +sp_test(sys, fd_path_survives_unlink_only_through_a_live_name) { + sp_test_skip_on_wasm() + + sp_sys_fd_t sandbox = SP_SYS_INVALID_FD; + sp_must_ok(t, sp_sys_open_dir_s(sp_fs_get_cwd(), sp_test_dir(t), 0, &sandbox)); + + sp_path_t opened = { .dir = sandbox, .sub = sp_str_lit("A") }; + sp_path_t alias = { .dir = sandbox, .sub = sp_str_lit("B") }; + sp_must_ok(t, sp_fs_create_file_at(opened)); + sp_must_ok(t, sp_fs_create_hard_link_at(opened, alias)); + + sp_sys_fd_t fd = SP_SYS_INVALID_FD; + sp_must_ok(t, sp_sys_open_s(opened.dir, opened.sub, SP_SYS_OPEN_MODE_RO, 0, &fd)); + sp_must_ok(t, sp_sys_unlink_s(opened.dir, opened.sub)); + + c8 buf [SP_PATH_MAX]; + u64 len = 0; + sp_err_t err = sp_sys_get_fd_path(fd, buf, sizeof(buf), &len); + if (err) { + sp_expect_err_eq(t, err, SP_ERR_SYS_NOT_FOUND); + } + else { + sp_expect(t, same_file(alias, sp_path_at_cwd(sp_str(buf, (u32)len)))); + } + + sp_sys_close(fd); + sp_sys_close(sandbox); + return SP_OK; +} + +sp_test(sys, fd_path_is_unsupported_on_wasm) { +#if !defined(SP_WASM) + return sp_test_skip(t, "wasm only"); +#else + c8 buf [SP_PATH_MAX]; + u64 len = 1; + sp_expect_err_eq(t, sp_sys_get_fd_path(sp_sys_get_root(0), buf, sizeof(buf), &len), SP_ERR_SYS_UNSUPPORTED); + sp_expect_eq(t, len, 0); + return SP_OK; +#endif +} + +sp_test(sys, fd_path_refuses_overflow) { + sp_test_skip_on_wasm() + + sp_mem_t mem = sp_test_arena(t); + sp_str_t path = sp_fs_join_path(mem, sp_test_dir(t), sp_str_lit("file.bin")); + sp_fs_create_file(path); + + sp_sys_fd_t fd = SP_SYS_INVALID_FD; + sp_must_ok(t, sp_sys_open_s(sp_fs_get_cwd(), path, SP_SYS_OPEN_MODE_PATH, 0, &fd)); + + c8 full [SP_PATH_MAX] = sp_zero; + u64 len = 0; + if (sp_sys_get_fd_path(fd, full, sizeof(full), &len)) { + sp_sys_close(fd); + sp_test_fail(t, "get_fd_path failed with a full-size buffer"); + return SP_OK; + } + + c8 buf [SP_PATH_MAX]; + sp_for(it, sizeof(buf)) buf[it] = (c8)0xAB; + u64 n = 0; + sp_err_t err = sp_sys_get_fd_path(fd, buf, len + 1, &n); + if (err) { + sp_test_fail(t, "exact fit failed: {}", sp_fmt_str(sp_err_str(err))); + } + else if (n != len) { + sp_test_fail(t, "exact fit returned {} but expected {}", sp_fmt_int((s64)n), sp_fmt_int((s64)len)); + } + else if (buf[len] != 0) { + sp_test_fail(t, "exact fit did not NUL-terminate"); + } + else if (!sp_mem_is_equal(buf, full, len)) { + sp_test_fail(t, "exact fit returned different path"); + } + + sp_for(it, sizeof(buf)) buf[it] = (c8)0xAB; + err = sp_sys_get_fd_path(fd, buf, len, &n); + if (err != SP_ERR_SYS_NAME_TOO_LONG) { + sp_test_fail(t, "no room for NUL: returned {} but expected SP_ERR_SYS_NAME_TOO_LONG", sp_fmt_str(sp_err_str(err))); + } + + sp_for(it, sizeof(buf)) buf[it] = (c8)0xAB; + err = sp_sys_get_fd_path(fd, buf, len - 1, &n); + if (err != SP_ERR_SYS_NAME_TOO_LONG) { + sp_test_fail(t, "undersized buffer: returned {} but expected SP_ERR_SYS_NAME_TOO_LONG", sp_fmt_str(sp_err_str(err))); + } + + sp_sys_close(fd); + return SP_OK; +} diff --git a/test/sys/path.c b/test/sys/path.c index b1e22030..000bb2a0 100644 --- a/test/sys/path.c +++ b/test/sys/path.c @@ -398,52 +398,3 @@ static sp_err_t run(sp_test_t* t, test_t* c) { sp_test_each_fn(sys, path, test_t, tests, run); sp_test_each_fn(sys, slash, test_t, slash_tests, run); - -sp_test(sys, get_fd_path_refuses_overflow) { - sp_mem_t mem = sp_test_arena(t); - sp_str_t path = sp_fs_join_path(mem, sp_test_dir(t), sp_str_lit("file.bin")); - sp_fs_create_file(path); - - sp_sys_fd_t fd = SP_SYS_INVALID_FD; - sp_must_ok(t, sp_sys_open_s(sp_sys_get_root(0), path, SP_SYS_OPEN_MODE_PATH, 0, &fd)); - - c8 full [SP_PATH_MAX] = sp_zero; - u64 len = 0; - if (sp_sys_get_fd_path(fd, full, sizeof(full), &len)) { - sp_sys_close(fd); - sp_test_fail(t, "get_fd_path failed with a full-size buffer"); - return SP_OK; - } - - c8 buf [SP_PATH_MAX]; - sp_for(it, sizeof(buf)) buf[it] = (c8)0xAB; - u64 n = 0; - sp_err_t err = sp_sys_get_fd_path(fd, buf, len + 1, &n); - if (err) { - sp_test_fail(t, "exact fit failed: {}", sp_fmt_str(sp_err_str(err))); - } - else if (n != len) { - sp_test_fail(t, "exact fit returned {} but expected {}", sp_fmt_int((s64)n), sp_fmt_int((s64)len)); - } - else if (buf[len] != 0) { - sp_test_fail(t, "exact fit did not NUL-terminate"); - } - else if (!sp_mem_is_equal(buf, full, len)) { - sp_test_fail(t, "exact fit returned different path"); - } - - sp_for(it, sizeof(buf)) buf[it] = (c8)0xAB; - err = sp_sys_get_fd_path(fd, buf, len, &n); - if (err != SP_ERR_SYS_NAME_TOO_LONG) { - sp_test_fail(t, "no room for NUL: returned {} but expected SP_ERR_SYS_NAME_TOO_LONG", sp_fmt_str(sp_err_str(err))); - } - - sp_for(it, sizeof(buf)) buf[it] = (c8)0xAB; - err = sp_sys_get_fd_path(fd, buf, len - 1, &n); - if (err != SP_ERR_SYS_NAME_TOO_LONG) { - sp_test_fail(t, "undersized buffer: returned {} but expected SP_ERR_SYS_NAME_TOO_LONG", sp_fmt_str(sp_err_str(err))); - } - - sp_sys_close(fd); - return SP_OK; -} diff --git a/test/sys/root.c b/test/sys/root.c index 88185772..8a217270 100644 --- a/test/sys/root.c +++ b/test/sys/root.c @@ -6,13 +6,40 @@ UTEST_F(sys_root, out_of_range_returns_invalid) { EXPECT_EQ(sp_sys_get_root(1024), SP_SYS_INVALID_FD); } -UTEST_F(sys_root, root_zero_has_a_path) { - sp_sys_fd_t root = sp_sys_get_root(0); - if (root == SP_SYS_INVALID_FD) UTEST_SKIP("no roots"); +UTEST_F(sys_root, out_of_range_has_no_label) { + c8 buf [SP_PATH_MAX] = sp_zero; + u64 len = 1; + EXPECT_EQ(sp_sys_get_root_label(1024, buf, sizeof(buf), &len), SP_ERR_SYS_BAD_FD); + EXPECT_EQ(len, 0); +} + +UTEST_F(sys_root, every_root_has_a_label) { + for (s32 it = 0; sp_sys_get_root(it) != SP_SYS_INVALID_FD; it++) { + c8 buf [SP_PATH_MAX]; + sp_for(at, sizeof(buf)) buf[at] = (c8)0xAB; + u64 len = 0; + EXPECT_EQ(sp_sys_get_root_label(it, buf, sizeof(buf), &len), SP_OK); + EXPECT_EQ(buf[len], 0); + } +} + +UTEST_F(sys_root, native_root_serves_everything) { + SKIP_ON_WASM() + + c8 buf [SP_PATH_MAX] = sp_zero; + u64 len = 1; + EXPECT_EQ(sp_sys_get_root_label(0, buf, sizeof(buf), &len), SP_OK); + EXPECT_EQ(len, 0); +} + +UTEST_F(sys_root, label_needs_room_for_nul) { + if (sp_sys_get_root(0) == SP_SYS_INVALID_FD) UTEST_SKIP("no roots"); c8 buf [SP_PATH_MAX] = sp_zero; u64 len = 0; - EXPECT_EQ(sp_sys_get_fd_path(root, buf, sizeof(buf), &len), SP_OK); - EXPECT_TRUE(len > 0); - EXPECT_TRUE(buf[len] == 0); + EXPECT_EQ(sp_sys_get_root_label(0, buf, sizeof(buf), &len), SP_OK); + + u64 n = 1; + EXPECT_EQ(sp_sys_get_root_label(0, buf, len, &n), SP_ERR_SYS_NAME_TOO_LONG); + EXPECT_EQ(n, 0); } diff --git a/test/sys/self_path.c b/test/sys/self_path.c index 86b4b238..27425644 100644 --- a/test/sys/self_path.c +++ b/test/sys/self_path.c @@ -23,9 +23,9 @@ static sp_err_t run(sp_test_t* t, test_t* c) { u64 n = 0; switch (c->fn) { case PATH_CWD: { - sp_sys_fd_t root = sp_sys_get_root(0); - if (root == SP_SYS_INVALID_FD) return sp_test_skip(t, "no roots"); - sp_must_ok(t, sp_sys_get_fd_path(root, buf, sizeof(buf), &n)); + sp_err_t err = sp_sys_get_fd_path(sp_fs_get_cwd(), buf, sizeof(buf), &n); + if (err == SP_ERR_SYS_UNSUPPORTED) return sp_test_skip(t, "not available"); + sp_must_ok(t, err); break; } case PATH_EXE: { diff --git a/tools/windows/sp/spn/amalgamation.vcxproj b/tools/windows/sp/spn/amalgamation.vcxproj index 4e850721..20aec49c 100644 --- a/tools/windows/sp/spn/amalgamation.vcxproj +++ b/tools/windows/sp/spn/amalgamation.vcxproj @@ -18,7 +18,7 @@ amalgamation 10.0 amalgamation - ..\..\..\..\test\main.c;..\..\..\..\test\test\axis.c;..\..\..\..\test\test\filter.c;..\..\..\..\test\test\matrix.c;..\..\..\..\test\test\resolve.c;..\..\..\..\test\test\sweep.c;..\..\..\..\test\test\tracking.c;..\..\..\..\test\sys\sys.c;..\..\..\..\test\sys\open.c;..\..\..\..\test\sys\path.c;..\..\..\..\test\sys\futex.c;..\..\..\..\test\sys\fd.c;..\..\..\..\test\sys\nanosleep.c;..\..\..\..\test\sys\timestamps.c;..\..\..\..\test\sys\perms.c;..\..\..\..\test\sys\self_path.c;..\..\..\..\test\sys\pipe\data.c;..\..\..\..\test\sys\pipe\blocking.c;..\..\..\..\test\sys\transfer.c;..\..\..\..\test\sys\readlink.c;..\..\..\..\test\sys\socket\data.c;..\..\..\..\test\sys\socket\listen.c;..\..\..\..\test\sys\socket\desc.c;..\..\..\..\test\sys\tty\contract.c;..\..\..\..\test\sys\tty\mode.c;..\..\..\..\test\sys\wait\event.c;..\..\..\..\test\sys\wait\wait.c;..\..\..\..\test\sys\inherit.c;..\..\..\..\test\fs\dir\*.c;..\..\..\..\test\fs\it\*.c;..\..\..\..\test\fs\wtf8\*.c;..\..\..\..\test\fs\*.c;..\..\..\..\test\fs\windows\nt_path.c;..\..\..\..\test\msvc\*.c;..\..\..\..\test\mem\uninitialized.c;..\..\..\..\test\atomic.c;..\..\..\..\test\sync\*.c;..\..\..\..\test\sync\os\*.c + ..\..\..\..\test\main.c;..\..\..\..\test\test\axis.c;..\..\..\..\test\test\filter.c;..\..\..\..\test\test\matrix.c;..\..\..\..\test\test\resolve.c;..\..\..\..\test\test\sweep.c;..\..\..\..\test\test\tracking.c;..\..\..\..\test\sys\sys.c;..\..\..\..\test\sys\open.c;..\..\..\..\test\sys\path.c;..\..\..\..\test\sys\futex.c;..\..\..\..\test\sys\fd.c;..\..\..\..\test\sys\fd_path.c;..\..\..\..\test\sys\nanosleep.c;..\..\..\..\test\sys\timestamps.c;..\..\..\..\test\sys\perms.c;..\..\..\..\test\sys\self_path.c;..\..\..\..\test\sys\pipe\data.c;..\..\..\..\test\sys\pipe\blocking.c;..\..\..\..\test\sys\transfer.c;..\..\..\..\test\sys\readlink.c;..\..\..\..\test\sys\socket\data.c;..\..\..\..\test\sys\socket\listen.c;..\..\..\..\test\sys\socket\desc.c;..\..\..\..\test\sys\tty\contract.c;..\..\..\..\test\sys\tty\mode.c;..\..\..\..\test\sys\wait\event.c;..\..\..\..\test\sys\wait\wait.c;..\..\..\..\test\sys\inherit.c;..\..\..\..\test\fs\dir\*.c;..\..\..\..\test\fs\it\*.c;..\..\..\..\test\fs\wtf8\*.c;..\..\..\..\test\fs\*.c;..\..\..\..\test\fs\windows\nt_path.c;..\..\..\..\test\msvc\*.c;..\..\..\..\test\mem\uninitialized.c;..\..\..\..\test\atomic.c;..\..\..\..\test\sync\*.c;..\..\..\..\test\sync\os\*.c ..\..\..\..\sp.h;..\..\..\..\sp\sp_test.h false diff --git a/tools/windows/sp/spn/sys.vcxproj b/tools/windows/sp/spn/sys.vcxproj index 3c7e40af..b144d8c3 100644 --- a/tools/windows/sp/spn/sys.vcxproj +++ b/tools/windows/sp/spn/sys.vcxproj @@ -18,7 +18,7 @@ sys 10.0 sys - ..\..\..\..\test\main.c;..\..\..\..\test\sys\sys.c;..\..\..\..\test\sys\open.c;..\..\..\..\test\sys\path.c;..\..\..\..\test\sys\futex.c;..\..\..\..\test\sys\fd.c;..\..\..\..\test\sys\nanosleep.c;..\..\..\..\test\sys\timestamps.c;..\..\..\..\test\sys\perms.c;..\..\..\..\test\sys\self_path.c;..\..\..\..\test\sys\pipe\data.c;..\..\..\..\test\sys\pipe\blocking.c;..\..\..\..\test\sys\transfer.c;..\..\..\..\test\sys\readlink.c;..\..\..\..\test\sys\socket\data.c;..\..\..\..\test\sys\socket\listen.c;..\..\..\..\test\sys\socket\desc.c;..\..\..\..\test\sys\tty\contract.c;..\..\..\..\test\sys\tty\mode.c;..\..\..\..\test\sys\wait\event.c;..\..\..\..\test\sys\wait\wait.c;..\..\..\..\test\sys\inherit.c + ..\..\..\..\test\main.c;..\..\..\..\test\sys\sys.c;..\..\..\..\test\sys\open.c;..\..\..\..\test\sys\path.c;..\..\..\..\test\sys\futex.c;..\..\..\..\test\sys\fd.c;..\..\..\..\test\sys\fd_path.c;..\..\..\..\test\sys\nanosleep.c;..\..\..\..\test\sys\timestamps.c;..\..\..\..\test\sys\perms.c;..\..\..\..\test\sys\self_path.c;..\..\..\..\test\sys\pipe\data.c;..\..\..\..\test\sys\pipe\blocking.c;..\..\..\..\test\sys\transfer.c;..\..\..\..\test\sys\readlink.c;..\..\..\..\test\sys\socket\data.c;..\..\..\..\test\sys\socket\listen.c;..\..\..\..\test\sys\socket\desc.c;..\..\..\..\test\sys\tty\contract.c;..\..\..\..\test\sys\tty\mode.c;..\..\..\..\test\sys\wait\event.c;..\..\..\..\test\sys\wait\wait.c;..\..\..\..\test\sys\inherit.c ..\..\..\..\sp.h;..\..\..\..\sp\sp_test.h false From d2770f068378a0f5476fecd82c18eb9b65d7c6dc Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Sun, 27 Sep 2026 04:15:17 -0400 Subject: [PATCH 38/72] fs: tighten path tests after design review --- test/fs/create_dir_sim.c | 40 ++++++ test/fs/get_cwd.c | 4 +- test/fs/get_ext.c | 35 ----- test/fs/get_stem.c | 34 ----- test/fs/is_sep.c | 2 - test/fs/join_path.c | 1 - test/fs/normalize_path.c | 1 + test/fs/parent_path.c | 1 + test/fs/replace_ext.c | 7 +- test/fs/resolve.c | 79 ++++++++---- test/fs/stem_ext.c | 42 ++++++ test/glob.c | 11 ++ test/sys/fd_path.c | 271 ++++++++++++++++++++++----------------- test/sys/path.c | 21 +++ test/sys/root.c | 12 ++ test/test/resolve.c | 16 ++- 16 files changed, 353 insertions(+), 224 deletions(-) create mode 100644 test/fs/create_dir_sim.c delete mode 100644 test/fs/get_ext.c delete mode 100644 test/fs/get_stem.c create mode 100644 test/fs/stem_ext.c diff --git a/test/fs/create_dir_sim.c b/test/fs/create_dir_sim.c new file mode 100644 index 00000000..5f5d5b7c --- /dev/null +++ b/test/fs/create_dir_sim.c @@ -0,0 +1,40 @@ +#include "sp.h" +#include "sp/sp_test.h" + +#define MAX_MKDIRS 64 + +typedef struct { + const c8* name; + const c8* target; +} test_t; + +static const test_t tests [] = { + { .name = "bare", .target = "A" }, + { .name = "nested", .target = "A/B" }, + { .name = "under_root", .target = "/A" }, + { .name = "dot", .target = "." }, + { .name = "root", .target = "/" }, + { .name = "empty", .target = "" }, +}; + +static u32 mkdirs; + +static sp_err_t mkdir_missing(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_file_perms_t perms) { + mkdirs++; + return mkdirs > MAX_MKDIRS ? SP_ERR_SYS_LOOP : SP_ERR_SYS_NOT_FOUND; +} + +static sp_err_t run(sp_test_t* t, test_t* c) { + sp_sys_vtable_t vt = sp_sys_vtable_platform; + vt.mkdir = mkdir_missing; + + mkdirs = 0; + const sp_sys_vtable_t* saved = sp_sys_set_vtable(&vt); + sp_err_t err = sp_fs_create_dir_at(sp_path_at_cwd(sp_cstr_as_str(c->target))); + sp_sys_set_vtable(saved); + + sp_expect_err_eq(t, err, SP_ERR_SYS_NOT_FOUND); + return SP_OK; +} + +sp_test_each_fn(fs, create_dir_sim, test_t, tests, run, .serial = true); diff --git a/test/fs/get_cwd.c b/test/fs/get_cwd.c index 4653164e..25ba4570 100644 --- a/test/fs/get_cwd.c +++ b/test/fs/get_cwd.c @@ -10,7 +10,7 @@ sp_test(fs, get_cwd_contract) { return SP_OK; } -sp_test(fs, get_cwd_unlinked_cwd_does_not_leak_deleted_suffix, .serial = true) { +sp_test(fs, get_cwd_unlinked_cwd_has_no_path, .serial = true) { // @spader #if !defined(SP_LINUX) return sp_test_skip(t, "unlinked-cwd is a Linux-only scenario"); @@ -28,7 +28,7 @@ sp_test(fs, get_cwd_unlinked_cwd_does_not_leak_deleted_suffix, .serial = true) { // restore cwd before any assertion can fail, so other tests aren't poisoned sp_sys_chdir_s(original); - sp_must(t, !sp_str_contains(cwd, sp_str_lit(" (deleted)"))); + sp_must(t, sp_str_empty(cwd)); return SP_OK; #endif } diff --git a/test/fs/get_ext.c b/test/fs/get_ext.c deleted file mode 100644 index db6b1376..00000000 --- a/test/fs/get_ext.c +++ /dev/null @@ -1,35 +0,0 @@ -#include "sp.h" -#include "sp/sp_test.h" - -typedef struct { - const c8* name; - sp_fs_path_kind_t kind; - const c8* input; - const c8* expect; -} test_t; - -static const test_t tests [] = { - { .name = "empty", .input = "", .expect = "" }, - { .name = "none", .input = "A", .expect = "" }, - { .name = "simple", .input = "A.B", .expect = "B" }, - { .name = "nested", .input = "A/B.txt", .expect = "txt" }, - { .name = "trailing_slash", .input = "A/B.txt/", .expect = "txt" }, - { .name = "last_dot_wins", .input = "A.B.C", .expect = "C" }, - { .name = "double_dot", .input = "A..txt", .expect = "txt" }, - { .name = "trailing_dot", .input = "A.", .expect = "" }, - { .name = "dot_in_dir", .input = "/A/B.C/D", .expect = "" }, - { .name = "hidden", .input = ".A", .expect = "" }, - { .name = "hidden_with_ext", .input = ".A.txt", .expect = "txt" }, - { .name = "dot", .input = ".", .expect = "" }, - { .name = "dotdot", .input = "..", .expect = "" }, - { .name = "root", .input = "/", .expect = "" }, - { .name = "backslash_is_a_name", .input = "A.B\\C", .expect = "B\\C" }, - - { .name = "windows_backslash", .kind = SP_FS_PATH_WINDOWS, .input = "C:\\A\\B.txt", .expect = "txt" }, - { .name = "windows_dot_in_dir", .kind = SP_FS_PATH_WINDOWS, .input = "A.B\\C", .expect = "" }, -}; - -sp_test_each(fs, get_ext, test_t, tests) { - sp_expect_str_eq_c(t, sp_fs_get_ext_for(sp_cstr_as_str(it->input), it->kind), it->expect); - return SP_OK; -} diff --git a/test/fs/get_stem.c b/test/fs/get_stem.c deleted file mode 100644 index 37683b34..00000000 --- a/test/fs/get_stem.c +++ /dev/null @@ -1,34 +0,0 @@ -#include "sp.h" -#include "sp/sp_test.h" - -typedef struct { - const c8* name; - sp_fs_path_kind_t kind; - const c8* input; - const c8* expect; -} test_t; - -static const test_t tests [] = { - { .name = "empty", .input = "", .expect = "" }, - { .name = "none", .input = "A", .expect = "A" }, - { .name = "simple", .input = "A.B", .expect = "A" }, - { .name = "nested", .input = "A/B.txt", .expect = "B" }, - { .name = "trailing_slash", .input = "A/B.txt/", .expect = "B" }, - { .name = "last_dot_wins", .input = "A.B.C", .expect = "A.B" }, - { .name = "double_dot", .input = "A..txt", .expect = "A." }, - { .name = "trailing_dot", .input = "A.", .expect = "A" }, - { .name = "hidden", .input = ".A", .expect = ".A" }, - { .name = "hidden_with_ext", .input = ".A.txt", .expect = ".A" }, - { .name = "dot", .input = ".", .expect = "." }, - { .name = "dotdot", .input = "..", .expect = ".." }, - { .name = "root", .input = "/", .expect = "" }, - { .name = "backslash_is_a_name", .input = "A\\B.txt", .expect = "A\\B" }, - - { .name = "windows_backslash", .kind = SP_FS_PATH_WINDOWS, .input = "C:\\A\\B.txt", .expect = "B" }, - { .name = "windows_dot_in_dir", .kind = SP_FS_PATH_WINDOWS, .input = "A.B\\C", .expect = "C" }, -}; - -sp_test_each(fs, get_stem, test_t, tests) { - sp_expect_str_eq_c(t, sp_fs_get_stem_for(sp_cstr_as_str(it->input), it->kind), it->expect); - return SP_OK; -} diff --git a/test/fs/is_sep.c b/test/fs/is_sep.c index d6580b5b..e857f6fb 100644 --- a/test/fs/is_sep.c +++ b/test/fs/is_sep.c @@ -12,8 +12,6 @@ static const test_t tests [] = { { .name = "slash", .input = '/', .posix = true, .windows = true }, { .name = "backslash", .input = '\\', .windows = true }, { .name = "letter", .input = 'A' }, - { .name = "colon", .input = ':' }, - { .name = "dot", .input = '.' }, }; sp_test_each(fs, is_sep, test_t, tests) { diff --git a/test/fs/join_path.c b/test/fs/join_path.c index e4bda650..118b3b40 100644 --- a/test/fs/join_path.c +++ b/test/fs/join_path.c @@ -31,7 +31,6 @@ static const test_t tests [] = { { .name = "windows_leading_rhs", .kind = SP_FS_PATH_WINDOWS, .a = "A", .b = "\\B", .expect = "A/B" }, { .name = "windows_drive", .kind = SP_FS_PATH_WINDOWS, .a = "C:/", .b = "A", .expect = "C:/A" }, { .name = "windows_drive_back", .kind = SP_FS_PATH_WINDOWS, .a = "C:\\", .b = "A", .expect = "C:\\A" }, - { .name = "windows_drive_bare", .kind = SP_FS_PATH_WINDOWS, .a = "C:", .b = "A", .expect = "C:/A" }, }; sp_test_each(fs, join_path, test_t, tests) { diff --git a/test/fs/normalize_path.c b/test/fs/normalize_path.c index 711acf05..e3a4958f 100644 --- a/test/fs/normalize_path.c +++ b/test/fs/normalize_path.c @@ -14,6 +14,7 @@ static const test_t tests [] = { { .name = "nested", .input = "A/B", .expect = "A/B" }, { .name = "trailing_slash", .input = "A/", .expect = "A" }, { .name = "root", .input = "/", .expect = "/" }, + { .name = "keeps_separator_run", .input = "A//B", .expect = "A//B" }, { .name = "keeps_dot", .input = "A/./B", .expect = "A/./B" }, { .name = "keeps_dotdot", .input = "A/B/../C", .expect = "A/B/../C" }, { .name = "backslash_is_a_name", .input = "A\\B\\", .expect = "A\\B\\" }, diff --git a/test/fs/parent_path.c b/test/fs/parent_path.c index 46513d7e..6689a6aa 100644 --- a/test/fs/parent_path.c +++ b/test/fs/parent_path.c @@ -13,6 +13,7 @@ static const test_t tests [] = { { .name = "bare", .input = "A", .expect = "." }, { .name = "bare_trailing", .input = "A/", .expect = "." }, { .name = "dot", .input = ".", .expect = "." }, + { .name = "dotdot", .input = "..", .expect = "." }, { .name = "nested", .input = "A/B", .expect = "A" }, { .name = "nested_trailing", .input = "A/B/", .expect = "A" }, { .name = "separator_run", .input = "A//B", .expect = "A" }, diff --git a/test/fs/replace_ext.c b/test/fs/replace_ext.c index 88babdf3..0737faf7 100644 --- a/test/fs/replace_ext.c +++ b/test/fs/replace_ext.c @@ -14,15 +14,12 @@ static const test_t tests [] = { { .name = "strip", .input = "A.c", .ext = "", .expect = "A" }, { .name = "add", .input = "A", .ext = "txt", .expect = "A.txt" }, { .name = "trailing_dot", .input = "A.", .ext = "txt", .expect = "A.txt" }, - { .name = "double_dot", .input = "A..txt", .ext = "md", .expect = "A..md" }, - { .name = "last_dot_wins", .input = "A.B.C", .ext = "d", .expect = "A.B.d" }, { .name = "hidden", .input = ".A", .ext = "txt", .expect = ".A.txt" }, - { .name = "nested", .input = "A/B.txt", .ext = "md", .expect = "A/B.md" }, + { .name = "keeps_dir", .input = "A.B/C.c", .ext = "o", .expect = "A.B/C.o" }, { .name = "trailing_slash", .input = "A/B.txt/", .ext = "md", .expect = "A/B.md" }, - { .name = "dot_in_dir", .input = "A.B/C", .ext = "d", .expect = "A.B/C.d" }, { .name = "backslash_is_a_name", .input = "A.B\\C", .ext = "d", .expect = "A.d" }, - { .name = "windows_dot_in_dir", .kind = SP_FS_PATH_WINDOWS, .input = "A.B\\C", .ext = "d", .expect = "A.B\\C.d" }, + { .name = "windows_keeps_dir", .kind = SP_FS_PATH_WINDOWS, .input = "A.B\\C", .ext = "d", .expect = "A.B\\C.d" }, { .name = "windows_trailing", .kind = SP_FS_PATH_WINDOWS, .input = "A\\B.c\\", .ext = "o", .expect = "A\\B.o" }, }; diff --git a/test/fs/resolve.c b/test/fs/resolve.c index 4bf9989b..097b2196 100644 --- a/test/fs/resolve.c +++ b/test/fs/resolve.c @@ -2,7 +2,14 @@ #include "sp/sp_test.h" #define MAX_ROOTS 4 -#define FD_BASE 9000 +#define ROOT_FD 9000 +#define CWD_FD 9100 + +typedef enum { + DIR_ROOT, + DIR_CWD, + DIR_NONE, +} dir_t; typedef struct { const c8* label; @@ -10,14 +17,15 @@ typedef struct { } root_t; typedef struct { + dir_t dir; s32 root; - bool invalid; const c8* sub; } expect_t; typedef struct { const c8* name; root_t roots [MAX_ROOTS]; + bool cwd; const c8* path; expect_t expect; } test_t; @@ -35,7 +43,7 @@ static s32 num_roots() { static sp_sys_fd_t get_root(s32 it) { if (it < 0 || it >= num_roots()) return SP_SYS_INVALID_FD; - return (sp_sys_fd_t)(FD_BASE + it); + return (sp_sys_fd_t)(ROOT_FD + it); } static sp_err_t get_root_label(s32 it, c8* buf, u64 size, u64* len) { @@ -55,11 +63,29 @@ static sp_err_t get_root_label(s32 it, c8* buf, u64 size, u64* len) { static const test_t tests [] = { { - .name = "relative_goes_to_cwd", + .name = "relative_goes_to_root_zero", .roots = { { .label = "/A" } }, .path = "A/B", .expect = { .sub = "A/B" }, }, + { + .name = "relative_goes_to_the_cwd", + .roots = { { .label = "/A" } }, + .cwd = true, + .path = "A/B", + .expect = { .dir = DIR_CWD, .sub = "A/B" }, + }, + { + .name = "relative_ignores_relative_labels", + .roots = { { .label = "." }, { .label = "A" } }, + .path = "A/B", + .expect = { .sub = "A/B" }, + }, + { + .name = "relative_without_roots", + .path = "A", + .expect = { .dir = DIR_NONE, .sub = "A" }, + }, { .name = "label_is_stripped", .roots = { { .label = "/B" }, { .label = "/A" } }, @@ -84,12 +110,6 @@ static const test_t tests [] = { .path = "/A/", .expect = { .root = 1, .sub = "." }, }, - { - .name = "label_trailing_separator_is_ignored", - .roots = { { .label = "/B" }, { .label = "/A/" } }, - .path = "/A/B", - .expect = { .root = 1, .sub = "B" }, - }, { .name = "label_matches_whole_components", .roots = { { .label = "/B" }, { .label = "/A" } }, @@ -109,16 +129,28 @@ static const test_t tests [] = { .expect = { .sub = "C" }, }, { - .name = "slash_label_takes_everything", + .name = "slash_label_strips_the_root", .roots = { { .label = "/B" }, { .label = "/" } }, .path = "/A/B", .expect = { .root = 1, .sub = "A/B" }, }, { - .name = "empty_label_takes_nothing", - .roots = { { .label = "" } }, + .name = "slash_label_is_dot", + .roots = { { .label = "/B" }, { .label = "/" } }, + .path = "/", + .expect = { .root = 1, .sub = "." }, + }, + { + .name = "empty_label_serves_everything", + .roots = { { .label = "/B" }, { .label = "" } }, + .path = "/A/B", + .expect = { .root = 1, .sub = "/A/B" }, + }, + { + .name = "any_label_beats_the_empty_label", + .roots = { { .label = "" }, { .label = "/" } }, .path = "/A/B", - .expect = { .sub = "/A/B" }, + .expect = { .root = 1, .sub = "A/B" }, }, { .name = "relative_label_never_matches", @@ -127,10 +159,11 @@ static const test_t tests [] = { .expect = { .root = 1, .sub = "B" }, }, { - .name = "unmatched_absolute_goes_to_cwd", + .name = "unserved_goes_to_the_cwd", .roots = { { .label = "/B" }, { .label = "/A" } }, + .cwd = true, .path = "/C", - .expect = { .sub = "/C" }, + .expect = { .dir = DIR_CWD, .sub = "/C" }, }, { .name = "unreadable_label_is_skipped", @@ -138,11 +171,6 @@ static const test_t tests [] = { .path = "/A/B", .expect = { .root = 1, .sub = "B" }, }, - { - .name = "no_roots", - .path = "A", - .expect = { .invalid = true, .sub = "A" }, - }, }; static sp_err_t run(sp_test_t* t, test_t* c) { @@ -152,12 +180,17 @@ static sp_err_t run(sp_test_t* t, test_t* c) { active = c; const sp_sys_vtable_t* saved = sp_sys_set_vtable(&vt); + if (c->cwd) sp_rt.cwd = (sp_sys_fd_t)CWD_FD; sp_path_t path = sp_path_resolve(sp_cstr_as_str(c->path)); + sp_rt.cwd = SP_SYS_INVALID_FD; sp_sys_set_vtable(saved); active = SP_NULLPTR; - sp_sys_fd_t dir = c->expect.invalid ? SP_SYS_INVALID_FD : (sp_sys_fd_t)(FD_BASE + c->expect.root); - sp_expect_eq(t, path.dir, dir); + switch (c->expect.dir) { + case DIR_ROOT: sp_expect_eq(t, path.dir, (sp_sys_fd_t)(ROOT_FD + c->expect.root)); break; + case DIR_CWD: sp_expect_eq(t, path.dir, (sp_sys_fd_t)CWD_FD); break; + case DIR_NONE: sp_expect_eq(t, path.dir, SP_SYS_INVALID_FD); break; + } sp_expect_str_eq(t, path.sub, sp_cstr_as_str(c->expect.sub)); return SP_OK; } diff --git a/test/fs/stem_ext.c b/test/fs/stem_ext.c new file mode 100644 index 00000000..c9bcf83f --- /dev/null +++ b/test/fs/stem_ext.c @@ -0,0 +1,42 @@ +#include "sp.h" +#include "sp/sp_test.h" + +typedef struct { + const c8* stem; + const c8* ext; +} expect_t; + +typedef struct { + const c8* name; + sp_fs_path_kind_t kind; + const c8* input; + expect_t expect; +} test_t; + +static const test_t tests [] = { + { .name = "empty", .input = "", .expect = { "", "" } }, + { .name = "none", .input = "A", .expect = { "A", "" } }, + { .name = "simple", .input = "A.B", .expect = { "A", "B" } }, + { .name = "nested", .input = "A/B.txt", .expect = { "B", "txt" } }, + { .name = "trailing_slash", .input = "A/B.txt/", .expect = { "B", "txt" } }, + { .name = "last_dot_wins", .input = "A.B.C", .expect = { "A.B", "C" } }, + { .name = "double_dot", .input = "A..txt", .expect = { "A.", "txt" } }, + { .name = "trailing_dot", .input = "A.", .expect = { "A", "" } }, + { .name = "dot_in_dir", .input = "/A/B.C/D", .expect = { "D", "" } }, + { .name = "hidden", .input = ".A", .expect = { ".A", "" } }, + { .name = "hidden_with_ext", .input = ".A.txt", .expect = { ".A", "txt" } }, + { .name = "dot", .input = ".", .expect = { ".", "" } }, + { .name = "dotdot", .input = "..", .expect = { "..", "" } }, + { .name = "root", .input = "/", .expect = { "", "" } }, + { .name = "backslash_is_a_name", .input = "A.B\\C", .expect = { "A", "B\\C" } }, + + { .name = "windows_backslash", .kind = SP_FS_PATH_WINDOWS, .input = "C:\\A\\B.txt", .expect = { "B", "txt" } }, + { .name = "windows_dot_in_dir", .kind = SP_FS_PATH_WINDOWS, .input = "A.B\\C", .expect = { "C", "" } }, +}; + +sp_test_each(fs, stem_ext, test_t, tests) { + sp_str_t path = sp_cstr_as_str(it->input); + sp_expect_str_eq_c(t, sp_fs_get_stem_for(path, it->kind), it->expect.stem); + sp_expect_str_eq_c(t, sp_fs_get_ext_for(path, it->kind), it->expect.ext); + return SP_OK; +} diff --git a/test/glob.c b/test/glob.c index cba3a996..8ad8410e 100644 --- a/test/glob.c +++ b/test/glob.c @@ -835,6 +835,17 @@ UTEST_F(glob, set_nested_path) { sp_glob_set_free(set); } +UTEST_F(glob, set_extension_matches_hidden_file) { + sp_glob_set_t* set = sp_glob_set_new(ut.mem.tracking); + sp_glob_set_add(set, "*.c"); + sp_glob_set_build(set); + + EXPECT_TRUE(sp_glob_set_match(set, sp_str_lit(".c"))); + EXPECT_TRUE(sp_glob_set_match(set, sp_str_lit("src/.c"))); + + sp_glob_set_free(set); +} + UTEST_F(glob, set_basename_literal) { sp_glob_set_t* set = sp_glob_set_new(ut.mem.tracking); sp_glob_set_add(set, "**/foo"); diff --git a/test/sys/fd_path.c b/test/sys/fd_path.c index 9aa46cb9..5f96f1d0 100644 --- a/test/sys/fd_path.c +++ b/test/sys/fd_path.c @@ -2,35 +2,109 @@ #include "sp/sp_test.h" typedef enum { - HANDLE_FILE, - HANDLE_DIR, - HANDLE_CWD, - HANDLE_PIPE, - HANDLE_GARBAGE, -} handle_t; + TARGET_FILE, + TARGET_DIR, + TARGET_CWD, + TARGET_PIPE, + TARGET_GARBAGE, +} target_t; typedef enum { FATE_KEPT, + FATE_UNLINKED, FATE_REMOVED, + FATE_RENAMED, + FATE_REPLACED, } fate_t; +typedef struct { + sp_err_t err; + const c8* path; +} expect_t; + typedef struct { const c8* name; - handle_t handle; + target_t target; + const c8* path; + const c8* alias; fate_t fate; - sp_err_t err; + expect_t expect; } test_t; static const test_t tests [] = { - { .name = "file_is_named", .handle = HANDLE_FILE }, - { .name = "dir_is_named", .handle = HANDLE_DIR }, - { .name = "cwd_is_named", .handle = HANDLE_CWD }, - { .name = "removed_file_has_no_name", .handle = HANDLE_FILE, .fate = FATE_REMOVED, .err = SP_ERR_SYS_NOT_FOUND }, - { .name = "removed_dir_has_no_name", .handle = HANDLE_DIR, .fate = FATE_REMOVED, .err = SP_ERR_SYS_NOT_FOUND }, - { .name = "pipe_has_no_name", .handle = HANDLE_PIPE, .err = SP_ERR_SYS_NOT_FOUND }, - { .name = "garbage_is_not_a_handle", .handle = HANDLE_GARBAGE, .err = SP_ERR_SYS_BAD_FD }, + { + .name = "file", + .target = TARGET_FILE, + .path = "A", + .expect = { .path = "A" }, + }, + { + .name = "dir", + .target = TARGET_DIR, + .path = "A", + .expect = { .path = "A" }, + }, + { + .name = "cwd", + .target = TARGET_CWD, + .expect = { .path = "." }, + }, + { + .name = "deleted_suffix_is_a_name", + .target = TARGET_FILE, + .path = "A (deleted)", + .expect = { .path = "A (deleted)" }, + }, + { + .name = "renamed", + .target = TARGET_FILE, + .path = "A", + .fate = FATE_RENAMED, + .expect = { .path = "B" }, + }, + { + .name = "unlinked", + .target = TARGET_FILE, + .path = "A", + .fate = FATE_UNLINKED, + .expect = { .err = SP_ERR_SYS_NOT_FOUND }, + }, + { + .name = "unlinked_with_a_live_alias", + .target = TARGET_FILE, + .path = "A", + .alias = "B", + .fate = FATE_UNLINKED, + .expect = { .err = SP_ERR_SYS_NOT_FOUND }, + }, + { + .name = "replaced", + .target = TARGET_FILE, + .path = "A", + .fate = FATE_REPLACED, + .expect = { .err = SP_ERR_SYS_NOT_FOUND }, + }, + { + .name = "removed_dir", + .target = TARGET_DIR, + .path = "A", + .fate = FATE_REMOVED, + .expect = { .err = SP_ERR_SYS_NOT_FOUND }, + }, + { + .name = "pipe", + .target = TARGET_PIPE, + .expect = { .err = SP_ERR_SYS_NOT_FOUND }, + }, + { + .name = "garbage", + .target = TARGET_GARBAGE, + .expect = { .err = SP_ERR_SYS_BAD_FD }, + }, }; +// Invalid by construction, not closed-then-reused: past any fd rlimit on +// POSIX, not a multiple of 4 on NT. No recycling race. #define GARBAGE_FD ((sp_sys_fd_t)0x55555) static bool same_file(sp_path_t a, sp_path_t b) { @@ -47,31 +121,36 @@ static sp_err_t run(sp_test_t* t, test_t* c) { sp_sys_fd_t sandbox = SP_SYS_INVALID_FD; sp_must_ok(t, sp_sys_open_dir_s(sp_fs_get_cwd(), sp_test_dir(t), 0, &sandbox)); - sp_path_t witness = { .dir = sandbox, .sub = sp_str_lit("A") }; + sp_path_t path = { .dir = sandbox, .sub = sp_cstr_as_str(c->path) }; + sp_path_t alias = { .dir = sandbox, .sub = sp_cstr_as_str(c->alias) }; + sp_path_t other = { .dir = sandbox, .sub = sp_str_lit("B") }; + sp_path_t expect = { .dir = sandbox, .sub = sp_cstr_as_str(c->expect.path) }; + sp_sys_pipe_t pipe = sp_zero; sp_sys_fd_t fd = SP_SYS_INVALID_FD; - switch (c->handle) { - case HANDLE_FILE: { - sp_must_ok(t, sp_fs_create_file_at(witness)); - sp_must_ok(t, sp_sys_open_s(witness.dir, witness.sub, SP_SYS_OPEN_MODE_RO, 0, &fd)); + switch (c->target) { + case TARGET_FILE: { + sp_must_ok(t, sp_fs_create_file_at(path)); + if (c->alias) sp_must_ok(t, sp_fs_create_hard_link_at(path, alias)); + sp_must_ok(t, sp_sys_open_s(path.dir, path.sub, SP_SYS_OPEN_MODE_RO, 0, &fd)); break; } - case HANDLE_DIR: { - sp_must_ok(t, sp_fs_create_dir_at(witness)); - sp_must_ok(t, sp_sys_open_dir_s(witness.dir, witness.sub, 0, &fd)); + case TARGET_DIR: { + sp_must_ok(t, sp_fs_create_dir_at(path)); + sp_must_ok(t, sp_sys_open_dir_s(path.dir, path.sub, 0, &fd)); break; } - case HANDLE_CWD: { - witness = sp_path_at_cwd(sp_str_lit(".")); - fd = witness.dir; + case TARGET_CWD: { + expect.dir = sp_fs_get_cwd(); + fd = expect.dir; break; } - case HANDLE_PIPE: { + case TARGET_PIPE: { sp_must_ok(t, sp_sys_pipe(&pipe, sp_zero_s(sp_sys_pipe_desc_t))); fd = pipe.r; break; } - case HANDLE_GARBAGE: { + case TARGET_GARBAGE: { fd = GARBAGE_FD; break; } @@ -81,11 +160,21 @@ static sp_err_t run(sp_test_t* t, test_t* c) { case FATE_KEPT: { break; } + case FATE_UNLINKED: { + sp_must_ok(t, sp_sys_unlink_s(path.dir, path.sub)); + break; + } case FATE_REMOVED: { - sp_err_t removed = c->handle == HANDLE_DIR ? - sp_sys_rmdir_s(witness.dir, witness.sub) : - sp_sys_unlink_s(witness.dir, witness.sub); - sp_must_ok(t, removed); + sp_must_ok(t, sp_sys_rmdir_s(path.dir, path.sub)); + break; + } + case FATE_RENAMED: { + sp_must_ok(t, sp_sys_rename_s(path.dir, path.sub, other.dir, other.sub)); + break; + } + case FATE_REPLACED: { + sp_must_ok(t, sp_sys_unlink_s(path.dir, path.sub)); + sp_must_ok(t, sp_fs_create_file_at(path)); break; } } @@ -95,25 +184,25 @@ static sp_err_t run(sp_test_t* t, test_t* c) { u64 len = 0; sp_err_t err = sp_sys_get_fd_path(fd, buf, sizeof(buf), &len); - sp_expect_err_eq(t, err, c->err); + sp_expect_err_eq(t, err, c->expect.err); if (!err) { sp_expect_eq(t, buf[len], 0); - sp_expect(t, same_file(witness, sp_path_at_cwd(sp_str(buf, (u32)len)))); + sp_expect(t, same_file(expect, sp_path_at_cwd(sp_str(buf, (u32)len)))); } - switch (c->handle) { - case HANDLE_FILE: - case HANDLE_DIR: { + switch (c->target) { + case TARGET_FILE: + case TARGET_DIR: { sp_sys_close(fd); break; } - case HANDLE_PIPE: { + case TARGET_PIPE: { sp_sys_close(pipe.r); sp_sys_close(pipe.w); break; } - case HANDLE_CWD: - case HANDLE_GARBAGE: { + case TARGET_CWD: + case TARGET_GARBAGE: { break; } } @@ -123,95 +212,43 @@ static sp_err_t run(sp_test_t* t, test_t* c) { sp_test_each_fn(sys, fd_path, test_t, tests, run); -sp_test(sys, fd_path_survives_unlink_only_through_a_live_name) { +typedef struct { + const c8* name; + s32 room; + sp_err_t err; +} room_t; + +static const room_t rooms [] = { + { .name = "fits_with_nul", .room = 1 }, + { .name = "no_room_for_nul", .room = 0, .err = SP_ERR_SYS_NAME_TOO_LONG }, + { .name = "too_small", .room = -1, .err = SP_ERR_SYS_NAME_TOO_LONG }, +}; + +sp_test_each(sys, fd_path_room, room_t, rooms) { sp_test_skip_on_wasm() sp_sys_fd_t sandbox = SP_SYS_INVALID_FD; sp_must_ok(t, sp_sys_open_dir_s(sp_fs_get_cwd(), sp_test_dir(t), 0, &sandbox)); - sp_path_t opened = { .dir = sandbox, .sub = sp_str_lit("A") }; - sp_path_t alias = { .dir = sandbox, .sub = sp_str_lit("B") }; - sp_must_ok(t, sp_fs_create_file_at(opened)); - sp_must_ok(t, sp_fs_create_hard_link_at(opened, alias)); - - sp_sys_fd_t fd = SP_SYS_INVALID_FD; - sp_must_ok(t, sp_sys_open_s(opened.dir, opened.sub, SP_SYS_OPEN_MODE_RO, 0, &fd)); - sp_must_ok(t, sp_sys_unlink_s(opened.dir, opened.sub)); - - c8 buf [SP_PATH_MAX]; + c8 full [SP_PATH_MAX]; u64 len = 0; - sp_err_t err = sp_sys_get_fd_path(fd, buf, sizeof(buf), &len); - if (err) { - sp_expect_err_eq(t, err, SP_ERR_SYS_NOT_FOUND); - } - else { - sp_expect(t, same_file(alias, sp_path_at_cwd(sp_str(buf, (u32)len)))); - } + sp_must_ok(t, sp_sys_get_fd_path(sandbox, full, sizeof(full), &len)); - sp_sys_close(fd); - sp_sys_close(sandbox); - return SP_OK; -} - -sp_test(sys, fd_path_is_unsupported_on_wasm) { -#if !defined(SP_WASM) - return sp_test_skip(t, "wasm only"); -#else c8 buf [SP_PATH_MAX]; - u64 len = 1; - sp_expect_err_eq(t, sp_sys_get_fd_path(sp_sys_get_root(0), buf, sizeof(buf), &len), SP_ERR_SYS_UNSUPPORTED); - sp_expect_eq(t, len, 0); - return SP_OK; -#endif -} - -sp_test(sys, fd_path_refuses_overflow) { - sp_test_skip_on_wasm() - - sp_mem_t mem = sp_test_arena(t); - sp_str_t path = sp_fs_join_path(mem, sp_test_dir(t), sp_str_lit("file.bin")); - sp_fs_create_file(path); - - sp_sys_fd_t fd = SP_SYS_INVALID_FD; - sp_must_ok(t, sp_sys_open_s(sp_fs_get_cwd(), path, SP_SYS_OPEN_MODE_PATH, 0, &fd)); - - c8 full [SP_PATH_MAX] = sp_zero; - u64 len = 0; - if (sp_sys_get_fd_path(fd, full, sizeof(full), &len)) { - sp_sys_close(fd); - sp_test_fail(t, "get_fd_path failed with a full-size buffer"); - return SP_OK; - } + sp_for(at, sizeof(buf)) buf[at] = (c8)0xAB; + u64 n = 1; + sp_err_t err = sp_sys_get_fd_path(sandbox, buf, (u64)((s64)len + it->room), &n); - c8 buf [SP_PATH_MAX]; - sp_for(it, sizeof(buf)) buf[it] = (c8)0xAB; - u64 n = 0; - sp_err_t err = sp_sys_get_fd_path(fd, buf, len + 1, &n); + sp_expect_err_eq(t, err, it->err); if (err) { - sp_test_fail(t, "exact fit failed: {}", sp_fmt_str(sp_err_str(err))); - } - else if (n != len) { - sp_test_fail(t, "exact fit returned {} but expected {}", sp_fmt_int((s64)n), sp_fmt_int((s64)len)); - } - else if (buf[len] != 0) { - sp_test_fail(t, "exact fit did not NUL-terminate"); + sp_expect_eq(t, n, 0); } - else if (!sp_mem_is_equal(buf, full, len)) { - sp_test_fail(t, "exact fit returned different path"); - } - - sp_for(it, sizeof(buf)) buf[it] = (c8)0xAB; - err = sp_sys_get_fd_path(fd, buf, len, &n); - if (err != SP_ERR_SYS_NAME_TOO_LONG) { - sp_test_fail(t, "no room for NUL: returned {} but expected SP_ERR_SYS_NAME_TOO_LONG", sp_fmt_str(sp_err_str(err))); - } - - sp_for(it, sizeof(buf)) buf[it] = (c8)0xAB; - err = sp_sys_get_fd_path(fd, buf, len - 1, &n); - if (err != SP_ERR_SYS_NAME_TOO_LONG) { - sp_test_fail(t, "undersized buffer: returned {} but expected SP_ERR_SYS_NAME_TOO_LONG", sp_fmt_str(sp_err_str(err))); + else { + sp_expect_eq(t, n, len); + sp_expect_eq(t, buf[len], 0); + sp_expect_mem_eq(t, buf, full, len); } - sp_sys_close(fd); + sp_sys_close(sandbox); return SP_OK; } diff --git a/test/sys/path.c b/test/sys/path.c index 000bb2a0..db7764a5 100644 --- a/test/sys/path.c +++ b/test/sys/path.c @@ -35,6 +35,7 @@ typedef struct { const c8* exists; const c8* not_exists; sp_fs_kind_t kind; + bool posix; } test_t; static const test_t tests [] = { @@ -129,6 +130,22 @@ static const test_t tests [] = { }; static const test_t slash_tests [] = { + { + .name = "mkdir_backslash_is_a_name", + .op = OP_MKDIR, + .path = "A\\", + .exists = "A\\", + .not_exists = "A", + .posix = true, + }, + { + .name = "stat_drive_is_a_name", + .op = OP_STAT, + .path = "A:/", + .file = "A:", + .kind = SP_FS_KIND_FILE, + .posix = true, + }, { .name = "unlink_slash_file", .op = OP_UNLINK, @@ -351,6 +368,10 @@ static bool check_err(sp_test_t* t, sp_err_t err, sp_err_t want) { } static sp_err_t run(sp_test_t* t, test_t* c) { + if (c->posix && sp_os_get_path_kind() != SP_FS_PATH_POSIX) { + return sp_test_skip(t, "posix paths only"); + } + sp_mem_t mem = sp_test_arena(t); sp_str_t sandbox = sp_test_dir(t); sp_sys_fd_t sandbox_fd = SP_SYS_INVALID_FD; diff --git a/test/sys/root.c b/test/sys/root.c index 8a217270..ca354681 100644 --- a/test/sys/root.c +++ b/test/sys/root.c @@ -20,6 +20,7 @@ UTEST_F(sys_root, every_root_has_a_label) { u64 len = 0; EXPECT_EQ(sp_sys_get_root_label(it, buf, sizeof(buf), &len), SP_OK); EXPECT_EQ(buf[len], 0); + EXPECT_TRUE(len <= 1 || buf[len - 1] != '/'); } } @@ -32,6 +33,17 @@ UTEST_F(sys_root, native_root_serves_everything) { EXPECT_EQ(len, 0); } +UTEST_F(sys_root, wasm_handles_have_no_path) { +#if !defined(SP_WASM) + UTEST_SKIP("wasm only"); +#else + c8 buf [SP_PATH_MAX] = sp_zero; + u64 len = 1; + EXPECT_EQ(sp_sys_get_fd_path(sp_sys_stdout, buf, sizeof(buf), &len), SP_ERR_SYS_UNSUPPORTED); + EXPECT_EQ(len, 0); +#endif +} + UTEST_F(sys_root, label_needs_room_for_nul) { if (sp_sys_get_root(0) == SP_SYS_INVALID_FD) UTEST_SKIP("no roots"); diff --git a/test/test/resolve.c b/test/test/resolve.c index 5c32770a..522a55d0 100644 --- a/test/test/resolve.c +++ b/test/test/resolve.c @@ -2,6 +2,7 @@ typedef struct { const c8* name; + sp_fs_path_kind_t kind; const c8* cwd; const c8* exe_dir; const c8* expect [16]; @@ -12,35 +13,40 @@ static const resolve_case_t resolve_cases [] = { .name = "disjoint", .cwd = "/a/b", .exe_dir = "/x/y", - .expect = { "/a/b", "/a", "/x/y", "/x" }, + .expect = { "/a/b", "/a", "/", "/x/y", "/x" }, }, { .name = "overlap", .cwd = "/x/repo", .exe_dir = "/x/repo/build/debug", - .expect = { "/x/repo", "/x", "/x/repo/build/debug", "/x/repo/build" }, + .expect = { "/x/repo", "/x", "/", "/x/repo/build/debug", "/x/repo/build" }, }, { .name = "windows", + .kind = SP_FS_PATH_WINDOWS, .cwd = "C:\\repo", .exe_dir = "C:\\repo\\build", - .expect = { "C:/repo", "C:", "C:/repo/build" }, + .expect = { "C:/repo", "C:/", "C:/repo/build" }, }, { .name = "trailing", .cwd = "/a/b/", .exe_dir = "", - .expect = { "/a/b", "/a" }, + .expect = { "/a/b", "/a", "/" }, }, { .name = "empty", .cwd = "", .exe_dir = "/x", - .expect = { "/x" }, + .expect = { "/x", "/" }, }, }; sp_test_each(golden, resolve, resolve_case_t, resolve_cases) { + if (it->kind != SP_FS_PATH_POSIX && it->kind != sp_os_get_path_kind()) { + return sp_test_skip(t, "paths of another kind"); + } + sp_da(sp_str_t) roots = sp_test_resolve_roots(sp_test_arena(t), sp_cstr_as_str(it->cwd), sp_cstr_as_str(it->exe_dir)); From 2802b53469d09f7347997aaf0f1e3138d2ddb5b0 Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Sun, 27 Sep 2026 04:16:20 -0400 Subject: [PATCH 39/72] fs: host kind path helpers, root labels, verified fd paths --- sp.h | 375 ++++++++++++++++++++++++++++++--------------------- sp/sp_glob.h | 6 +- 2 files changed, 223 insertions(+), 158 deletions(-) diff --git a/sp.h b/sp.h index 384de5e9..cf3eff56 100644 --- a/sp.h +++ b/sp.h @@ -8723,7 +8723,22 @@ sp_sys_fd_t sp_sys_get_root_p(s32 it) { sp_err_t sp_sys_get_root_label_p(s32 it, c8* buf, u64 size, u64* len) { *len = 0; - return SP_ERR_SYS_UNSUPPORTED; + +#if defined(SP_WIN32) || defined(SP_LINUX) || defined(SP_MACOS) || defined(SP_COSMO) + if (it != 0) return SP_ERR_SYS_BAD_FD; + if (!size) return SP_ERR_SYS_NAME_TOO_LONG; + buf[0] = 0; + return SP_OK; + +#elif defined(SP_WASM) + sp_try(sp_sys_wasi_prestat_name((__wasi_fd_t)(3 + it), buf, size, len)); + *len = sp_sys_trim_path(buf, (u32)*len); + buf[*len] = 0; + return SP_OK; + +#else + #error "sp_sys_get_root_label" +#endif } ////////////////////// @@ -11085,28 +11100,37 @@ sp_err_t sp_sys_set_times_s(sp_sys_fd_t fd, sp_str_t path, sp_sys_timespec_t ati //////////////////////// // SP_SYS_GET_FD_PATH // //////////////////////// -sp_err_t sp_sys_get_fd_path_p(sp_sys_fd_t fd, c8* buf, u64 size, u64* len) { - *len = 0; - #if defined(SP_WIN32) - u16 wbuf[SP_PATH_MAX]; +SP_PRIVATE sp_err_t sp_sys_get_handle_metadata(sp_sys_fd_t fd, sp_sys_file_meta_t* meta) { + return sp_sys_file_meta_from_nt_path(fd, sp_str_lit("."), meta, false); +} + +SP_PRIVATE sp_err_t sp_sys_read_fd_path(sp_sys_fd_t fd, c8 buf [SP_PATH_MAX], u64* len) { + u16 wbuf [SP_PATH_MAX]; DWORD wlen = GetFinalPathNameByHandleW((HANDLE)fd, (LPWSTR)wbuf, SP_PATH_MAX, 0); if (wlen == 0) return sp_sys_err_from_win32(GetLastError()); if (wlen >= SP_PATH_MAX) return SP_ERR_SYS_NAME_TOO_LONG; sp_wide_str_t wide = sp_sys_dos_from_nt_wtf16(wbuf, wlen); - SP_ALIGNED c8 u8buf[SP_PATH_MAX * 3 + 1]; + SP_ALIGNED c8 u8buf [SP_PATH_MAX * 3 + 1]; sp_mem_fixed_t fixed = sp_mem_fixed(u8buf, sizeof(u8buf)); - sp_str_t utf8; + sp_str_t utf8 = sp_zero; sp_try(sp_wtf16_to_wtf8(sp_mem_fixed_as_allocator(&fixed), wide, &utf8)); - s64 n = sp_sys_win32_copy_path(utf8, buf, size); + + s64 n = sp_sys_win32_copy_path(utf8, buf, SP_PATH_MAX); if (n < 0) return SP_ERR_SYS_NAME_TOO_LONG; *len = (u64)n; return SP_OK; +} #elif defined(SP_LINUX) +SP_PRIVATE sp_err_t sp_sys_get_handle_metadata(sp_sys_fd_t fd, sp_sys_file_meta_t* meta) { + return sp_sys_get_file_metadata_p(fd, meta); +} + +SP_PRIVATE sp_err_t sp_sys_read_fd_path(sp_sys_fd_t fd, c8 buf [SP_PATH_MAX], u64* len) { if (fd == SP_AT_FDCWD) { - s64 rc = sp_syscall(SP_SYSCALL_NUM_GETCWD, buf, size); + s64 rc = sp_syscall(SP_SYSCALL_NUM_GETCWD, buf, SP_PATH_MAX); if (rc == -SP_ERANGE) return SP_ERR_SYS_NAME_TOO_LONG; if (sp_sys_is_err(rc)) return sp_sys_err_from_errno(-rc); *len = (u64)rc - 1; @@ -11116,33 +11140,60 @@ sp_err_t sp_sys_get_fd_path_p(sp_sys_fd_t fd, c8* buf, u64 size, u64* len) { c8 proc [64] = sp_zero; sp_fmt_buf(proc, 64, "/proc/self/fd/{}", sp_fmt_int(fd)); - c8 rbuf [SP_PATH_MAX]; - s64 rc = sp_syscall(SP_SYSCALL_NUM_READLINKAT, SP_AT_FDCWD, proc, rbuf, SP_PATH_MAX); + s64 rc = sp_syscall(SP_SYSCALL_NUM_READLINKAT, SP_AT_FDCWD, proc, buf, SP_PATH_MAX); if (sp_sys_is_err(rc)) return sp_sys_err_from_errno(-rc); - if (rc == SP_PATH_MAX || (u64)rc >= size) return SP_ERR_SYS_NAME_TOO_LONG; - sp_mem_copy(buf, rbuf, (u64)rc); - buf[rc] = 0; + if (rc == SP_PATH_MAX) return SP_ERR_SYS_NAME_TOO_LONG; *len = (u64)rc; return SP_OK; +} #elif defined(SP_MACOS) || defined(SP_COSMO) - c8 resolved [SP_PATH_MAX]; +SP_PRIVATE sp_err_t sp_sys_get_handle_metadata(sp_sys_fd_t fd, sp_sys_file_meta_t* meta) { + if (fd == SP_AT_FDCWD) return sp_sys_get_path_metadata_p(fd, ".", 1, meta); + return sp_sys_get_file_metadata_p(fd, meta); +} + +SP_PRIVATE sp_err_t sp_sys_read_fd_path(sp_sys_fd_t fd, c8 buf [SP_PATH_MAX], u64* len) { if (fd == SP_AT_FDCWD) { - if (!getcwd(resolved, sizeof(resolved))) return sp_sys_err_from_errno(errno); + if (!getcwd(buf, SP_PATH_MAX)) return sp_sys_err_from_errno(errno); } - else if (fcntl((s32)fd, F_GETPATH, resolved) < 0) { + else if (fcntl((s32)fd, F_GETPATH, buf) < 0) { return sp_sys_err_from_errno(errno); } - u64 n = sp_cstr_len(resolved); + *len = sp_cstr_len(buf); + return SP_OK; +} +#endif + +sp_err_t sp_sys_get_fd_path_p(sp_sys_fd_t fd, c8* buf, u64 size, u64* len) { + *len = 0; + +#if defined(SP_WIN32) || defined(SP_LINUX) || defined(SP_MACOS) || defined(SP_COSMO) + sp_sys_file_meta_t handle = sp_zero; + sp_err_t err = sp_sys_get_handle_metadata(fd, &handle); + if (err == SP_ERR_SYS_BAD_FD) return err; + if (err) return SP_ERR_SYS_NOT_FOUND; + + c8 path [SP_PATH_MAX]; + u64 n = 0; + err = sp_sys_read_fd_path(fd, path, &n); + if (err == SP_ERR_SYS_NAME_TOO_LONG) return err; + if (err) return SP_ERR_SYS_NOT_FOUND; + + sp_sys_file_meta_t named = sp_zero; + if (sp_sys_get_link_metadata_p(sp_sys_get_root_p(0), path, (u32)n, &named)) return SP_ERR_SYS_NOT_FOUND; + if (named.id != handle.id) return SP_ERR_SYS_NOT_FOUND; + if (named.device != handle.device) return SP_ERR_SYS_NOT_FOUND; + if (n >= size) return SP_ERR_SYS_NAME_TOO_LONG; - sp_mem_copy(buf, resolved, n); + sp_mem_copy(buf, path, n); buf[n] = 0; *len = n; return SP_OK; #elif defined(SP_WASM) - return sp_sys_wasi_prestat_name((__wasi_fd_t)fd, buf, size, len); + return SP_ERR_SYS_UNSUPPORTED; #else #error "sp_sys_get_fd_path" @@ -20037,106 +20088,122 @@ sp_str_r sp_fmt_write_f64_buf(c8* buffer, u64 len, f64 value, u32 precision) { return str; } -sp_str_t sp_fs_get_name(sp_str_t path) { - // The only valid separator is '/'. If you're calling this function on a path - // that might contain a '\', normalize it first. Garbage in, garbage out. - s32 it = sp_str_find_c8_reverse(path, '/'); - return sp_str_suffix(path, ((s32)path.len) - it - 1); +bool sp_fs_is_sep_for(c8 c, sp_fs_path_kind_t kind) { + switch (kind) { + case SP_FS_PATH_POSIX: return c == '/'; + case SP_FS_PATH_WINDOWS: return c == '/' || c == '\\'; + } + SP_UNREACHABLE_RETURN(false); } -sp_str_t sp_fs_get_name_for(sp_str_t path, sp_fs_path_kind_t kind) { - if (kind != SP_FS_PATH_WINDOWS) return sp_fs_get_name(path); +SP_PRIVATE u32 sp_fs_root_len(sp_str_t path, sp_fs_path_kind_t kind) { + if (sp_str_empty(path)) return 0; + if (sp_fs_is_sep_for(path.data[0], kind)) return 1; - s32 slash = sp_str_find_c8_reverse(path, '/'); - s32 backslash = sp_str_find_c8_reverse(path, '\\'); - return sp_str_suffix(path, ((s32)path.len) - sp_max(slash, backslash) - 1); + switch (kind) { + case SP_FS_PATH_POSIX: return 0; + case SP_FS_PATH_WINDOWS: return path.len >= 3 && path.data[1] == ':' && sp_fs_is_sep_for(path.data[2], kind) ? 3 : 0; + } + SP_UNREACHABLE_RETURN(0); } -sp_str_t sp_fs_trim_path(sp_str_t path) { - if (sp_str_empty(path)) return path; +SP_PRIVATE u32 sp_fs_stem_len(sp_str_t name) { + u32 hidden = 0; + while (hidden < name.len && name.data[hidden] == '.') hidden++; - while (!sp_str_empty(path) && !sp_fs_is_root(path)) { - switch (sp_str_back(path)) { - case '/': - case '\\': { - path.len--; - break; - } - default: { - return path; - } - } + for (u32 it = name.len; it > hidden; it--) { + if (name.data[it - 1] == '.') return it - 1; } + return name.len; +} +bool sp_fs_is_absolute_for(sp_str_t path, sp_fs_path_kind_t kind) { + return sp_fs_root_len(path, kind) > 0; +} + +sp_str_t sp_fs_trim_path_for(sp_str_t path, sp_fs_path_kind_t kind) { + u32 root = sp_fs_root_len(path, kind); + while (path.len > root && sp_fs_is_sep_for(sp_str_back(path), kind)) path.len--; return path; } -sp_str_t sp_fs_parent_path(sp_str_t path) { - if (sp_str_empty(path)) return path; - if (sp_fs_is_root(path)) return path; +bool sp_fs_is_root_for(sp_str_t path, sp_fs_path_kind_t kind) { + u32 root = sp_fs_root_len(path, kind); + return root && sp_fs_trim_path_for(path, kind).len == root; +} - path = sp_fs_trim_path(path); +sp_str_t sp_fs_get_name_for(sp_str_t path, sp_fs_path_kind_t kind) { + path = sp_fs_trim_path_for(path, kind); - s32 index = sp_str_find_c8_reverse(path, '/'); - return index == SP_STR_NO_MATCH ? sp_str_lit("") : sp_str_prefix(path, index); + u32 root = sp_fs_root_len(path, kind); + u32 start = path.len; + while (start > root && !sp_fs_is_sep_for(path.data[start - 1], kind)) start--; + return sp_str_suffix(path, (s32)(path.len - start)); } -sp_str_t sp_fs_get_ext(sp_str_t path) { - for (u32 index = 0; index < path.len; index++) { - c8 c = sp_str_at_reverse(path, index); - - switch (c) { - case '.': return sp_str_sub_reverse(path, 0, index); - case '/': return sp_str_sub_reverse(path, 0, 0); - default: break; - } - } +sp_str_t sp_fs_parent_path_for(sp_str_t path, sp_fs_path_kind_t kind) { + path = sp_fs_trim_path_for(path, kind); + if (sp_str_empty(path)) return path; - return sp_str_sub_reverse(path, 0, 0); + sp_str_t name = sp_fs_get_name_for(path, kind); + sp_str_t parent = sp_fs_trim_path_for(sp_str_prefix(path, (s32)(path.len - name.len)), kind); + return sp_str_empty(parent) ? sp_str_lit(".") : parent; } -sp_str_t sp_fs_get_stem(sp_str_t path) { - sp_str_t file_name = sp_fs_get_name(path); - if (!file_name.len) return path; - - sp_str_t extension = sp_fs_get_ext(path); +sp_str_t sp_fs_get_stem_for(sp_str_t path, sp_fs_path_kind_t kind) { + sp_str_t name = sp_fs_get_name_for(path, kind); + return sp_str_prefix(name, (s32)sp_fs_stem_len(name)); +} - sp_str_t stem = { - .data = file_name.data, - .len = file_name.len - extension.len, - }; +sp_str_t sp_fs_get_ext_for(sp_str_t path, sp_fs_path_kind_t kind) { + sp_str_t name = sp_fs_get_name_for(path, kind); + u32 dot = sp_fs_stem_len(name); + return sp_str_suffix(name, (s32)(name.len - sp_min(dot + 1, name.len))); +} - if (sp_str_back(stem) == '.') stem.len--; +sp_str_t sp_fs_replace_ext_for(sp_mem_t mem, sp_str_t path, sp_str_t ext, sp_fs_path_kind_t kind) { + path = sp_fs_trim_path_for(path, kind); - return stem; + sp_str_t name = sp_fs_get_name_for(path, kind); + sp_str_t base = sp_str_prefix(path, (s32)(path.len - name.len + sp_fs_stem_len(name))); + return sp_str_empty(ext) ? + sp_str_copy(mem, base) : + sp_str_join(mem, base, ext, sp_str_lit(".")); } -bool sp_fs_is_sep(c8 c) { - return c == '/' || c == '\\'; +sp_str_t sp_fs_join_path_for(sp_mem_t mem, sp_str_t a, sp_str_t b, sp_fs_path_kind_t kind) { + a = sp_fs_trim_path_for(a, kind); + b = sp_fs_trim_path_for(b, kind); + if (sp_str_empty(a)) return sp_str_copy(mem, b); + + while (!sp_str_empty(b) && sp_fs_is_sep_for(b.data[0], kind)) b = sp_str_suffix(b, (s32)b.len - 1); + if (sp_str_empty(b)) return sp_str_copy(mem, a); + if (sp_fs_is_sep_for(sp_str_back(a), kind)) return sp_str_concat(mem, a, b); + return sp_str_join(mem, a, b, sp_str_lit("/")); } -bool sp_fs_is_absolute_for(sp_str_t path, sp_fs_path_kind_t kind) { - if (path.len == 0) return false; +sp_str_t sp_fs_normalize_path_for(sp_mem_t mem, sp_str_t path, sp_fs_path_kind_t kind) { + path = sp_fs_trim_path_for(path, kind); - if (sp_fs_is_sep(path.data[0])) { - return true; + c8* buffer = sp_alloc_n(mem, c8, path.len); + sp_for(it, path.len) { + buffer[it] = sp_fs_is_sep_for(path.data[it], kind) ? '/' : path.data[it]; } + return sp_str(buffer, path.len); +} - if (kind != SP_FS_PATH_WINDOWS) return false; +bool sp_fs_is_sep(c8 c) { + return sp_fs_is_sep_for(c, sp_os_get_path_kind()); +} - return (path.len >= 3 && path.data[1] == ':' && sp_fs_is_sep(path.data[2])); +bool sp_fs_is_root(sp_str_t path) { + return sp_fs_is_root_for(path, sp_os_get_path_kind()); } bool sp_fs_is_absolute(sp_str_t path) { return sp_fs_is_absolute_for(path, sp_os_get_path_kind()); } -bool sp_fs_is_root(sp_str_t path) { - if (path.len == 1 && sp_fs_is_sep(path.data[0])) return true; - if (path.len == 3 && path.data[1] == ':' && sp_fs_is_sep(path.data[2])) return true; - return false; -} - bool sp_fs_is_absolute_w(sp_wide_str_t path) { if (path.len == 0) return false; if (path.data[0] == '/' || path.data[0] == '\\') return true; @@ -20145,83 +20212,79 @@ bool sp_fs_is_absolute_w(sp_wide_str_t path) { return false; } -sp_str_t sp_fs_normalize_path(sp_mem_t mem, sp_str_t path) { - path = sp_fs_trim_path(path); - - c8* buffer = (c8*)sp_mem_allocator_alloc(mem, path.len); - sp_for(i, path.len) { - c8 c = path.data[i]; - buffer[i] = (c == '\\' ? '/' : c); - } - return (sp_str_t) { .data = buffer, .len = path.len }; -} - -sp_str_t sp_fs_join_path(sp_mem_t mem, sp_str_t a, sp_str_t b) { - a = sp_fs_trim_path(a); - b = sp_fs_trim_path(b); - if (sp_str_empty(a)) return sp_str_copy(mem, b); - if (sp_str_empty(b)) return sp_str_copy(mem, a); - if (sp_str_back(a) == '/' || sp_str_back(a) == '\\') { - return sp_str_concat(mem, a, b); - } - return sp_str_join(mem, a, b, sp_str_lit("/")); +sp_str_t sp_fs_trim_path(sp_str_t path) { + return sp_fs_trim_path_for(path, sp_os_get_path_kind()); } -sp_str_t sp_fs_replace_ext(sp_mem_t mem, sp_str_t path, sp_str_t ext) { - sp_str_t stripped = sp_str_strip_right(path, sp_fs_get_ext(path)); - if (sp_str_back(stripped) == '.') stripped.len--; - return sp_str_empty(ext) ? - sp_str_copy(mem, stripped) : - sp_str_join(mem, stripped, ext, sp_str_lit(".")); +sp_str_t sp_fs_get_name(sp_str_t path) { + return sp_fs_get_name_for(path, sp_os_get_path_kind()); } -sp_sys_fd_t sp_fs_get_cwd() { - if (sp_rt.cwd != SP_SYS_INVALID_FD) return sp_rt.cwd; - return sp_sys_get_root(0); +sp_str_t sp_fs_parent_path(sp_str_t path) { + return sp_fs_parent_path_for(path, sp_os_get_path_kind()); } -bool sp_fs_is_sep_for(c8 c, sp_fs_path_kind_t kind) { - return false; +sp_str_t sp_fs_get_stem(sp_str_t path) { + return sp_fs_get_stem_for(path, sp_os_get_path_kind()); } -bool sp_fs_is_root_for(sp_str_t path, sp_fs_path_kind_t kind) { - return false; +sp_str_t sp_fs_get_ext(sp_str_t path) { + return sp_fs_get_ext_for(path, sp_os_get_path_kind()); } -sp_str_t sp_fs_trim_path_for(sp_str_t path, sp_fs_path_kind_t kind) { - return sp_zero_s(sp_str_t); +sp_str_t sp_fs_replace_ext(sp_mem_t mem, sp_str_t path, sp_str_t ext) { + return sp_fs_replace_ext_for(mem, path, ext, sp_os_get_path_kind()); } -sp_str_t sp_fs_parent_path_for(sp_str_t path, sp_fs_path_kind_t kind) { - return sp_zero_s(sp_str_t); +sp_str_t sp_fs_join_path(sp_mem_t mem, sp_str_t a, sp_str_t b) { + return sp_fs_join_path_for(mem, a, b, sp_os_get_path_kind()); } -sp_str_t sp_fs_get_ext_for(sp_str_t path, sp_fs_path_kind_t kind) { - return sp_zero_s(sp_str_t); +sp_str_t sp_fs_normalize_path(sp_mem_t mem, sp_str_t path) { + return sp_fs_normalize_path_for(mem, path, sp_os_get_path_kind()); } -sp_str_t sp_fs_get_stem_for(sp_str_t path, sp_fs_path_kind_t kind) { - return sp_zero_s(sp_str_t); +sp_sys_fd_t sp_fs_get_cwd() { + if (sp_rt.cwd != SP_SYS_INVALID_FD) return sp_rt.cwd; + return sp_sys_get_root(0); } -sp_str_t sp_fs_replace_ext_for(sp_mem_t mem, sp_str_t path, sp_str_t ext, sp_fs_path_kind_t kind) { - return sp_zero_s(sp_str_t); +sp_path_t sp_path_at_cwd(sp_str_t sub) { + return (sp_path_t) { .dir = sp_fs_get_cwd(), .sub = sub }; } -sp_str_t sp_fs_join_path_for(sp_mem_t mem, sp_str_t a, sp_str_t b, sp_fs_path_kind_t kind) { - return sp_zero_s(sp_str_t); -} +SP_PRIVATE s32 sp_fs_match_label(sp_str_t path, sp_str_t label) { + if (sp_str_empty(label)) return 0; + if (!sp_str_starts_with(path, label)) return SP_STR_NO_MATCH; -sp_str_t sp_fs_normalize_path_for(sp_mem_t mem, sp_str_t path, sp_fs_path_kind_t kind) { - return sp_zero_s(sp_str_t); -} + u32 at = label.len; + bool boundary = at == path.len || sp_fs_is_sep(path.data[at]) || sp_fs_is_sep(sp_str_back(label)); + if (!boundary) return SP_STR_NO_MATCH; -sp_path_t sp_path_at_cwd(sp_str_t sub) { - return (sp_path_t) { .dir = sp_fs_get_cwd(), .sub = sub }; + while (at < path.len && sp_fs_is_sep(path.data[at])) at++; + return (s32)at; } sp_path_t sp_path_resolve(sp_str_t path) { - return sp_path_at_cwd(path); + sp_path_t best = sp_path_at_cwd(path); + if (!sp_fs_is_absolute(path)) return best; + + s32 deepest = SP_STR_NO_MATCH; + for (s32 it = 0;; it++) { + sp_sys_fd_t root = sp_sys_get_root(it); + if (root == SP_SYS_INVALID_FD) return best; + + c8 buf [SP_PATH_MAX]; + u64 len = 0; + if (sp_sys_get_root_label(it, buf, SP_PATH_MAX, &len)) continue; + + s32 matched = sp_fs_match_label(path, sp_str(buf, (u32)len)); + if (matched <= deepest) continue; + + sp_str_t rest = sp_str_suffix(path, (s32)path.len - matched); + deepest = matched; + best = (sp_path_t) { .dir = root, .sub = sp_str_empty(rest) ? sp_str_lit(".") : rest }; + } } sp_fs_kind_t sp_fs_get_link_kind_at(sp_path_t path) { @@ -20416,7 +20479,6 @@ SP_PRIVATE sp_err_t sp_fs_mkdir_at(sp_sys_fd_t dir, sp_str_t path) { } sp_err_t sp_fs_create_dir_at(sp_path_t path) { - path.sub = sp_fs_trim_path(path.sub); sp_err_t err = sp_fs_mkdir_at(path.dir, path.sub); if (err != SP_ERR_SYS_NOT_FOUND) return err; @@ -20744,24 +20806,22 @@ sp_err_t sp_fs_remove_dir(sp_str_t path) { */ sp_err_t sp_fs_remove_dir_at(sp_path_t path) { - sp_str_t sub = sp_fs_trim_path(path.sub); - sp_str_t name = sp_fs_get_name_for(sub, sp_os_get_path_kind()); + sp_str_t name = sp_fs_get_name(path.sub); if (sp_str_empty(name) || sp_str_equal(name, sp_str_lit(".")) || sp_str_equal(name, sp_str_lit(".."))) { return SP_ERR_SYS_INVALID; } sp_sys_file_meta_t meta = sp_zero; - sp_try(sp_sys_get_link_metadata_s(path.dir, sub, &meta)); + sp_try(sp_sys_get_link_metadata_s(path.dir, path.sub, &meta)); switch (meta.kind) { case SP_FS_KIND_DIR: break; - case SP_FS_KIND_SYMLINK: return sp_sys_unlink_s(path.dir, sub); + case SP_FS_KIND_SYMLINK: return sp_sys_unlink_s(path.dir, path.sub); case SP_FS_KIND_FILE: case SP_FS_KIND_NONE: return SP_ERR_SYS_NOT_DIR; } - sp_path_t root = { .dir = path.dir, .sub = sub }; sp_mem_arena_marker_t s = sp_mem_begin_scratch(); - sp_fs_it_t it = sp_fs_it_new_at(s.mem, root, SP_SYS_OPEN_DIR_NO_FOLLOW); + sp_fs_it_t it = sp_fs_it_new_at(s.mem, path, SP_SYS_OPEN_DIR_NO_FOLLOW); sp_err_t err = it.err; while (!err && sp_fs_it_next(&it)) { @@ -20792,30 +20852,33 @@ sp_err_t sp_fs_remove_dir_at(sp_path_t path) { sp_mem_end_scratch(s); if (err) return err; - err = sp_sys_rmdir_s(root.dir, root.sub); + err = sp_sys_rmdir_s(path.dir, path.sub); return err == SP_ERR_SYS_NOT_FOUND ? SP_OK : err; } static sp_atomic_s32_t sp_fs_atomic_sequence; -SP_PRIVATE sp_str_t sp_fs_temp_name(sp_str_t dir, sp_str_t name, c8 buf [SP_PATH_MAX]) { +SP_PRIVATE sp_err_t sp_fs_temp_name(sp_str_t dir, sp_str_t name, c8 buf [SP_PATH_MAX], sp_str_t* temp) { sp_tm_epoch_t now = sp_tm_now_epoch(); u64 stamp = (now.s << 20) ^ (u64)now.ns; u64 sequence = (u64)(u32)sp_atomic_s32_add(&sp_fs_atomic_sequence, 1, SP_ATOMIC_SEQ_CST); + c8 leaf_buf [SP_PATH_MAX]; sp_io_mem_writer_t io = sp_zero; - sp_io_mem_writer_from_buffer(&io, buf, SP_PATH_MAX); - if (sp_str_empty(dir)) { - sp_fmt_io(&io.base, ".{}.{}.{}.tmp", sp_fmt_str(name), sp_fmt_uint(stamp), sp_fmt_uint(sequence)); - } else { - sp_fmt_io(&io.base, "{}/.{}.{}.{}.tmp", sp_fmt_str(dir), sp_fmt_str(name), sp_fmt_uint(stamp), sp_fmt_uint(sequence)); - } - return sp_io_mem_writer_as_str(&io); + sp_io_mem_writer_from_buffer(&io, leaf_buf, SP_PATH_MAX); + sp_fmt_io(&io.base, ".{}.{}.{}.tmp", sp_fmt_str(name), sp_fmt_uint(stamp), sp_fmt_uint(sequence)); + + sp_str_t leaf = sp_io_mem_writer_as_str(&io); + if (dir.len + leaf.len + 1 >= SP_PATH_MAX) return SP_ERR_SYS_NAME_TOO_LONG; + + sp_mem_fixed_t fixed = sp_mem_fixed_ex(buf, SP_PATH_MAX, 1); + *temp = sp_fs_join_path(sp_mem_fixed_as_allocator(&fixed), dir, leaf); + return SP_OK; } SP_PRIVATE sp_err_t sp_fs_create_parent_at(sp_sys_fd_t dir, sp_str_t path) { sp_str_t parent = sp_fs_parent_path(path); - if (sp_str_empty(parent)) return SP_OK; + if (sp_str_equal(parent, sp_fs_trim_path(path))) return SP_OK; sp_err_t err = sp_fs_create_dir_at((sp_path_t) { .dir = dir, .sub = parent }); return err == SP_ERR_SYS_EXISTS ? SP_ERR_SYS_NOT_DIR : err; } @@ -20824,11 +20887,11 @@ SP_PRIVATE sp_err_t sp_fs_atomic_create(sp_fs_atomic_t* af, sp_sys_fd_t dir, sp_ *af = sp_zero_s(sp_fs_atomic_t); af->dir = dir; af->path = path; - af->temp = sp_fs_temp_name(temp_dir, sp_fs_get_name(path), af->temp_buf); u32 flags = SP_SYS_OPEN_CREATE | SP_SYS_OPEN_EXCLUSIVE; sp_sys_fd_t fd = SP_SYS_INVALID_FD; - sp_err_t err = sp_sys_open_s(dir, af->temp, SP_SYS_OPEN_MODE_WO, flags, &fd); + sp_err_t err = sp_fs_temp_name(temp_dir, sp_fs_get_name(path), af->temp_buf, &af->temp); + if (!err) err = sp_sys_open_s(dir, af->temp, SP_SYS_OPEN_MODE_WO, flags, &fd); if (err) { *af = sp_zero_s(sp_fs_atomic_t); return err; diff --git a/sp/sp_glob.h b/sp/sp_glob.h index b6a7deda..a746fec0 100644 --- a/sp/sp_glob.h +++ b/sp/sp_glob.h @@ -596,10 +596,12 @@ bool sp_glob_match(sp_glob_t* g, sp_str_t path) { } sp_glob_candidate_t sp_glob_candidate_new(sp_str_t path) { + sp_str_t basename = sp_fs_get_name(path); + s32 dot = sp_str_find_c8_reverse(basename, '.'); return (sp_glob_candidate_t){ .path = path, - .basename = sp_fs_get_name(path), - .ext = sp_fs_get_ext(path), + .basename = basename, + .ext = dot == SP_STR_NO_MATCH ? sp_zero_s(sp_str_t) : sp_str_suffix(basename, (s32)basename.len - dot - 1), }; } From 51c22542f19c3f9a38d1fcc8b52b38223a04a44f Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Sun, 27 Sep 2026 04:16:44 -0400 Subject: [PATCH 40/72] example: one caller shape per fs example --- Makefile | 7 ++- example/fs/absolute.c | 14 ++--- example/fs/iterator.c | 142 +++++++++++++----------------------------- example/fs/roots.c | 10 +-- example/ls.c | 4 +- example/wc.c | 3 +- example/zero_copy.c | 5 +- spn.toml | 4 +- 8 files changed, 66 insertions(+), 123 deletions(-) diff --git a/Makefile b/Makefile index 5c481903..afac35bf 100644 --- a/Makefile +++ b/Makefile @@ -110,7 +110,12 @@ endif TESTS = amalg app array asset cli etc env format fmon glob ht io math process ps rb str sys thread time mem prompt leak qsort term BENCHES = glob heap EXAMPLES = app array cargo cli format hash_table io zero_copy ls palette post prompt prompt_fancy serve signal tls wc -EXAMPLES += fs/absolute fs/config fs/iterator fs/ls fs/preopen fs/roots fs/workspace +EXAMPLES += fs/config fs/iterator fs/ls fs/roots fs/workspace +ifneq (,$(findstring wasm32,$(TRIPLE))) + EXAMPLES += fs/preopen +else + EXAMPLES += fs/absolute +endif TRIPLES = \ x86_64-linux-none x86_64-linux-gnu x86_64-linux-musl \ aarch64-linux-none aarch64-linux-gnu aarch64-linux-musl \ diff --git a/example/fs/absolute.c b/example/fs/absolute.c index fa6d19e3..d16931d1 100644 --- a/example/fs/absolute.c +++ b/example/fs/absolute.c @@ -1,20 +1,18 @@ #define SP_IMPLEMENTATION #include "sp.h" -static sp_err_t save(sp_mem_t mem, sp_str_t dir, sp_str_t file, sp_str_t* settings) { - sp_try(sp_fs_create_dir_at(sp_path_resolve(dir))); - sp_try(sp_fs_write_atomic_at(sp_path_resolve(file), sp_str_lit("theme = \"dark\"\n"))); - return sp_io_read_file_at(mem, sp_path_resolve(file), settings); +static sp_err_t save(sp_mem_t mem, sp_str_t file, sp_str_t* settings) { + sp_try(sp_fs_create_dir(sp_fs_parent_path(file))); + sp_try(sp_fs_write_atomic(file, sp_str_lit("theme = \"dark\"\n"))); + return sp_io_read_file(mem, file, settings); } s32 run(s32 num_args, const c8** args) { sp_mem_arena_marker_t s = sp_mem_begin_scratch(); - sp_str_t config = sp_fs_get_config_path(s.mem); - sp_str_t dir = sp_fs_join_path(s.mem, config, sp_str_lit("sp")); - sp_str_t file = sp_fs_join_path(s.mem, dir, sp_str_lit("settings.toml")); + sp_str_t file = sp_fs_join_path(s.mem, sp_fs_get_config_path(s.mem), sp_str_lit("sp/settings.toml")); sp_str_t settings = sp_zero; - sp_err_t err = sp_str_empty(config) ? SP_ERR_SYS_NOT_FOUND : save(s.mem, dir, file, &settings); + sp_err_t err = save(s.mem, file, &settings); if (err) { sp_log("{.red}: {}", sp_fmt_str(file), sp_fmt_str(sp_err_str(err))); } diff --git a/example/fs/iterator.c b/example/fs/iterator.c index f15b5497..7b0e0911 100644 --- a/example/fs/iterator.c +++ b/example/fs/iterator.c @@ -1,119 +1,63 @@ #define SP_IMPLEMENTATION #include "sp.h" -#include "sp/sp_cli.h" -typedef struct { - sp_str_t dir; - bool walk; -} args_t; - -typedef struct { - args_t args; - sp_mem_heap_t* heap; - sp_mem_t mem; -} ctx_t; +static bool is_hidden(sp_fs_entry_t entry) { + return sp_str_starts_with(entry.name, sp_str_lit(".")); +} -void print_entry(sp_fs_entry_t entry, u32 depth) { +static void print_entry(sp_fs_entry_t entry, u32 depth) { sp_print("{:$}", sp_fmt_uint(depth * 2), sp_fmt_cstr("")); - sp_print("{.gray}", sp_fmt_cstr("│ ")); switch (entry.kind) { - case SP_FS_KIND_DIR: sp_log("{.blue .bold}", sp_fmt_str(entry.name)); break; - case SP_FS_KIND_FILE: sp_log("{}", sp_fmt_str(entry.name)); break; - case SP_FS_KIND_SYMLINK: sp_log("{.cyan} -> {.gray}", sp_fmt_str(entry.name), sp_fmt_str(entry.path)); break; - case SP_FS_KIND_NONE: break; + case SP_FS_KIND_DIR: { + sp_log("{.blue}/", sp_fmt_str(entry.name)); + break; + } + case SP_FS_KIND_FILE: { + sp_log("{}", sp_fmt_str(entry.name)); + break; + } + case SP_FS_KIND_SYMLINK: { + sp_log("{.cyan}", sp_fmt_str(entry.name)); + break; + } + case SP_FS_KIND_NONE: { + break; + } } - } -void report(sp_str_t dir, sp_err_t err) { - if (err) sp_log("{.red}: {}", sp_fmt_str(dir), sp_fmt_str(sp_err_str(err))); -} +s32 run(s32 num_args, const c8** args) { + sp_str_t dir = num_args > 1 ? sp_cstr_as_str(args[1]) : sp_str_lit("."); -void iterate(sp_str_t dir, sp_path_t path) { sp_mem_arena_marker_t s = sp_mem_begin_scratch(); - - print_entry((sp_fs_entry_t) { - .kind = SP_FS_KIND_DIR, - .name = sp_fs_get_name(dir) - }, 0); - - sp_fs_it_t it = sp_fs_it_new_at(s.mem, path, 0); + sp_fs_it_t it = sp_fs_it_new_at(s.mem, sp_path_resolve(dir), 0); + u32 dirs = 0; while (sp_fs_it_next(&it)) { - if (it.yield == SP_FS_IT_LEAVE) { - continue; - } - print_entry(it.entry, sp_da_size(it.stack)); + switch (it.yield) { + case SP_FS_IT_ENTRY: { + if (is_hidden(it.entry)) break; - // switch (it.entry.kind) { - // case SP_FS_KIND_DIR: sp_fs_it_enter(&it); - // case SP_FS_KIND_FILE: - // case SP_FS_KIND_SYMLINK: - // case SP_FS_KIND_NONE: break; - // } + print_entry(it.entry, sp_da_size(it.stack) - 1); + if (it.entry.kind == SP_FS_KIND_DIR) sp_fs_it_enter(&it); + break; + } + case SP_FS_IT_LEAVE: { + dirs++; + break; + } + } } - report(dir, it.err); - sp_mem_end_scratch(s); -} -void walk(sp_str_t dir, sp_path_t path) { - sp_mem_arena_marker_t s = sp_mem_begin_scratch(); - sp_fs_it_t it = sp_fs_it_new_at(s.mem, path, 0); - while (sp_fs_it_walk(&it)) { - print_entry(it.entry, sp_da_size(it.stack) - 1); + sp_err_t err = it.err; + if (err) { + sp_log("{.red}: {}", sp_fmt_str(dir), sp_fmt_str(sp_err_str(err))); } - report(dir, it.err); - sp_mem_end_scratch(s); -} - -sp_cli_result_t command(sp_cli_t* cli) { - ctx_t* ctx = sp_ptr_cast(ctx_t*, cli->user_data); - args_t args = ctx->args; - - sp_str_t dir = sp_str_empty(args.dir) ? sp_str_lit(".") : args.dir; - sp_path_t path = sp_path_resolve(dir); - - if (args.walk) { - walk(dir, path); - } else { - iterate(dir, path); + else { + sp_log("{} {.gray}", sp_fmt_uint(dirs), sp_fmt_cstr("directories")); } - return SP_CLI_OK; -} - -s32 run(s32 num_args, const c8** args) { - ctx_t ctx = sp_zero; - ctx.heap = sp_mem_heap_new(); - ctx.mem = sp_mem_heap_as_allocator(ctx.heap); - sp_cli_cmd_t root = { - .name = "filesystem", - .summary = "", - .opts = { - { - .brief = 'w', - .name = "walk", - .kind = SP_CLI_OPT_BOOLEAN, - .summary = "Walk (as opposed to flat traversal)", - .ptr = &ctx.args.walk - }, - }, - .args = { - { - .name = "dir", - .summary = "The directory", - .arity = SP_CLI_ARG_OPTIONAL, - .kind = SP_CLI_OPT_STR, - .ptr = &ctx.args.dir - } - }, - .handler = command, - }; - - return sp_cli_main((sp_cli_desc_t) { - .root = &root, - .args = args, - .num_args = num_args, - .user_data = &ctx - }); + sp_fs_it_deinit(&it); + sp_mem_end_scratch(s); + return err ? 1 : 0; } SP_MAIN(run) diff --git a/example/fs/roots.c b/example/fs/roots.c index e64e1a20..9282cf20 100644 --- a/example/fs/roots.c +++ b/example/fs/roots.c @@ -1,15 +1,15 @@ #define SP_IMPLEMENTATION #include "sp.h" -static void print_root(sp_sys_fd_t root) { +static void print_root(s32 it) { c8 buf [SP_PATH_MAX]; u64 len = 0; - sp_err_t err = sp_sys_get_fd_path(root, buf, sizeof(buf), &len); + sp_err_t err = sp_sys_get_root_label(it, buf, sizeof(buf), &len); if (err) { - sp_log("{:>4} {.red}", sp_fmt_int(root), sp_fmt_str(sp_err_str(err))); + sp_log("{:>4} {.red}", sp_fmt_int(sp_sys_get_root(it)), sp_fmt_str(sp_err_str(err))); } else { - sp_log("{:>4} {.cyan}", sp_fmt_int(root), sp_fmt_str(sp_str(buf, (u32)len))); + sp_log("{:>4} {.cyan}", sp_fmt_int(sp_sys_get_root(it)), sp_fmt_str(sp_str(buf, (u32)len))); } } @@ -26,7 +26,7 @@ static void print_path(sp_str_t path) { s32 run(s32 num_args, const c8** args) { for (s32 it = 0; sp_sys_get_root(it) != SP_SYS_INVALID_FD; it++) { - print_root(sp_sys_get_root(it)); + print_root(it); } sp_for_range(it, 1, (u32)num_args) { diff --git a/example/ls.c b/example/ls.c index 9b5d8828..51f4ac68 100644 --- a/example/ls.c +++ b/example/ls.c @@ -10,9 +10,7 @@ s32 compare_entries(const void* pa, const void* pb) { s32 run(s32 num_args, const c8** args) { sp_mem_heap_t* heap = sp_mem_heap_new(); sp_mem_t mem = sp_mem_heap_as_allocator(heap); - sp_str_t cwd = sp_fs_get_cwd_path(mem); - sp_str_t dir = cwd; - if (num_args == 2) dir = sp_fs_join_path(mem, cwd, sp_str_view(args[1])); + sp_str_t dir = num_args == 2 ? sp_cstr_as_str(args[1]) : sp_str_lit("."); sp_da(sp_fs_entry_t) entries; if (sp_fs_collect(mem, dir, &entries)) { diff --git a/example/wc.c b/example/wc.c index 884b1e53..f4a9b3a9 100644 --- a/example/wc.c +++ b/example/wc.c @@ -10,8 +10,7 @@ s32 run(s32 num_args, const c8** args) { sp_mem_heap_t* heap = sp_mem_heap_new(); sp_mem_t mem = sp_mem_heap_as_allocator(heap); - sp_str_t cwd = sp_fs_get_cwd_path(mem); - sp_str_t path = sp_fs_join_path(mem, cwd, sp_str_view(args[1])); + sp_str_t path = sp_cstr_as_str(args[1]); sp_str_t content = sp_zero; sp_io_read_file(mem, path, &content); diff --git a/example/zero_copy.c b/example/zero_copy.c index d948e871..d514726f 100644 --- a/example/zero_copy.c +++ b/example/zero_copy.c @@ -176,9 +176,8 @@ s32 run(s32 num_args, const c8** args) { sp_mem_heap_t* heap = sp_mem_heap_new(); sp_mem_t mem = sp_mem_heap_as_allocator(heap); - sp_str_t cwd = sp_fs_get_cwd_path(mem); - sp_str_t src = sp_str_concat(mem, cwd, sp_str_lit("/io.src")); - sp_str_t dst = sp_str_concat(mem, cwd, sp_str_lit("/io.dst")); + sp_str_t src = sp_str_lit("io.src"); + sp_str_t dst = sp_str_lit("io.dst"); u64 size_bytes = (u64)PERF_FILE_SIZE_MB * 1024u * 1024u; sp_log("preparing {} MiB source at {}", sp_fmt_uint(PERF_FILE_SIZE_MB), sp_fmt_str(src)); diff --git a/spn.toml b/spn.toml index 7aeacfc2..51d4a067 100644 --- a/spn.toml +++ b/spn.toml @@ -102,8 +102,8 @@ name = "msvc" source = ["example/msvc.c"] [[example]] -name = "filesystem" -source = ["example/filesystem.c"] +name = "iterator" +source = ["example/fs/iterator.c"] [profile.sanitizer] linkage = "shared" From 26f7dc8df4ecce9751fc9e50d55535b320f6101a Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Mon, 28 Sep 2026 16:48:37 -0400 Subject: [PATCH 41/72] small glob/iterator fix --- README.md | 18 +++++++++++------- example/fs/iterator.c | 7 ++++--- sp/sp_glob.h | 10 ++++++++-- test/glob.c | 40 ++++++++++++++++++++++++++++++++++++++++ test/sys/fd_path.c | 13 +++++++++++-- 5 files changed, 74 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 10888af5..29bf8719 100644 --- a/README.md +++ b/README.md @@ -36,13 +36,16 @@ s32 compare_entries(const void* pa, const void* pb) { return sp_str_compare_alphabetical(a->name, b->name); } -s32 main(s32 num_args, const c8** args) { - sp_mem_t mem = sp_mem_os_new(); - sp_str_t cwd = sp_fs_get_cwd_path(mem); - sp_str_t dir = cwd; - if (num_args == 2) dir = sp_fs_join_path(mem, cwd, sp_cstr_as_str(args[1])); - - sp_da(sp_fs_entry_t) entries = sp_fs_collect(mem, dir); +s32 run(s32 num_args, const c8** args) { + sp_mem_heap_t* heap = sp_mem_heap_new(); + sp_mem_t mem = sp_mem_heap_as_allocator(heap); + sp_str_t dir = num_args == 2 ? sp_cstr_as_str(args[1]) : sp_str_lit("."); + + sp_da(sp_fs_entry_t) entries; + if (sp_fs_collect(mem, dir, &entries)) { + sp_log("could not read {}", sp_fmt_str(dir)); + return 1; + } sp_da_sort(entries, compare_entries); sp_da_for(entries, it) { @@ -56,6 +59,7 @@ s32 main(s32 num_args, const c8** args) { } return 0; } +SP_MAIN(run) ``` A few modules showcased in this example: - `sp_mem_t` is an allocator; everything that allocates takes one. In the example, we use the default heap allocator. diff --git a/example/fs/iterator.c b/example/fs/iterator.c index 7b0e0911..f577a0a9 100644 --- a/example/fs/iterator.c +++ b/example/fs/iterator.c @@ -31,14 +31,15 @@ s32 run(s32 num_args, const c8** args) { sp_mem_arena_marker_t s = sp_mem_begin_scratch(); sp_fs_it_t it = sp_fs_it_new_at(s.mem, sp_path_resolve(dir), 0); + sp_err_t err = it.err; u32 dirs = 0; - while (sp_fs_it_next(&it)) { + while (!err && sp_fs_it_next(&it)) { switch (it.yield) { case SP_FS_IT_ENTRY: { if (is_hidden(it.entry)) break; print_entry(it.entry, sp_da_size(it.stack) - 1); - if (it.entry.kind == SP_FS_KIND_DIR) sp_fs_it_enter(&it); + if (it.entry.kind == SP_FS_KIND_DIR) err = sp_fs_it_enter(&it); break; } case SP_FS_IT_LEAVE: { @@ -47,8 +48,8 @@ s32 run(s32 num_args, const c8** args) { } } } + if (!err) err = it.err; - sp_err_t err = it.err; if (err) { sp_log("{.red}: {}", sp_fmt_str(dir), sp_fmt_str(sp_err_str(err))); } diff --git a/sp/sp_glob.h b/sp/sp_glob.h index a746fec0..38d5497b 100644 --- a/sp/sp_glob.h +++ b/sp/sp_glob.h @@ -118,6 +118,7 @@ SP_IMP bool sp_glob_match_class(sp_glob_token_t* tok, c8 c); SP_IMP bool sp_glob_match_alternates(sp_glob_token_t* tok, sp_da(sp_glob_token_t) tokens, u32 t, sp_str_t path, u32 p); SP_IMP bool sp_glob_match_tokens(sp_da(sp_glob_token_t) tokens, sp_str_t path); SP_IMP sp_glob_strategy_t sp_glob_detect_strategy(sp_glob_t* glob); +SP_IMP sp_str_t sp_glob_basename(sp_str_t path); SP_IMP sp_glob_candidate_t sp_glob_candidate_new(sp_str_t path); SP_IMP void sp_glob_set_ht_add(sp_mem_t mem, sp_glob_set_index_table_t* ht, sp_str_t key, u32 idx); SP_IMP void sp_glob_set_ht_collect(sp_glob_set_index_table_t ht, sp_str_t key, sp_da(u32)* out); @@ -559,6 +560,11 @@ sp_glob_t* sp_glob_new_str(sp_mem_t mem, sp_str_t pattern) { return g; } +sp_str_t sp_glob_basename(sp_str_t path) { + s32 slash = sp_str_find_c8_reverse(path, '/'); + return slash == SP_STR_NO_MATCH ? path : sp_str_suffix(path, (s32)path.len - slash - 1); +} + bool sp_glob_match(sp_glob_t* g, sp_str_t path) { SP_ASSERT(g); @@ -567,7 +573,7 @@ bool sp_glob_match(sp_glob_t* g, sp_str_t path) { return sp_str_equal(g->literal, path); } case SP_GLOB_STRATEGY_BASENAME_LITERAL: { - sp_str_t basename = sp_fs_get_name(path); + sp_str_t basename = sp_glob_basename(path); return sp_str_equal(g->literal, basename); } case SP_GLOB_STRATEGY_EXTENSION: { @@ -596,7 +602,7 @@ bool sp_glob_match(sp_glob_t* g, sp_str_t path) { } sp_glob_candidate_t sp_glob_candidate_new(sp_str_t path) { - sp_str_t basename = sp_fs_get_name(path); + sp_str_t basename = sp_glob_basename(path); s32 dot = sp_str_find_c8_reverse(basename, '.'); return (sp_glob_candidate_t){ .path = path, diff --git a/test/glob.c b/test/glob.c index 8ad8410e..3f5d6097 100644 --- a/test/glob.c +++ b/test/glob.c @@ -984,6 +984,46 @@ UTEST_F(glob, set_duplicate_extension) { sp_glob_set_free(set); } +typedef struct { + const c8* pattern; + const c8* path; +} no_match_test_t; + +void run_no_match_test(int* utest_result, sp_mem_t mem, no_match_test_t t) { + sp_str_t path = sp_cstr_as_str(t.path); + + sp_glob_t* g = sp_glob_new(mem, t.pattern); + EXPECT_FALSE(sp_glob_match(g, path)); + sp_glob_free(g); + + sp_glob_set_t* set = sp_glob_set_new(mem); + sp_glob_set_add(set, t.pattern); + sp_glob_set_build(set); + EXPECT_FALSE(sp_glob_set_match(set, path)); + sp_glob_set_free(set); +} + +UTEST_F(glob, no_match_extension_trailing_slash) { + run_no_match_test(utest_result, ut.mem.tracking, (no_match_test_t) { + .pattern = "*.c", + .path = "A.c/", + }); +} + +UTEST_F(glob, no_match_basename_trailing_slash) { + run_no_match_test(utest_result, ut.mem.tracking, (no_match_test_t) { + .pattern = "**/A", + .path = "B/A/", + }); +} + +UTEST_F(glob, no_match_basename_backslash) { + run_no_match_test(utest_result, ut.mem.tracking, (no_match_test_t) { + .pattern = "**/A", + .path = "B\\A", + }); +} + UTEST_F(glob, parse_empty) { run_parse_test(utest_result, ut.mem.arena, (parse_test_t){ .pattern = "", diff --git a/test/sys/fd_path.c b/test/sys/fd_path.c index 5f96f1d0..2d4b08b4 100644 --- a/test/sys/fd_path.c +++ b/test/sys/fd_path.c @@ -27,6 +27,7 @@ typedef struct { target_t target; const c8* path; const c8* alias; + const c8* decoy; fate_t fate; expect_t expect; } test_t; @@ -77,6 +78,14 @@ static const test_t tests [] = { .fate = FATE_UNLINKED, .expect = { .err = SP_ERR_SYS_NOT_FOUND }, }, + { + .name = "unlinked_beside_a_deleted_suffix", + .target = TARGET_FILE, + .path = "A", + .decoy = "A (deleted)", + .fate = FATE_UNLINKED, + .expect = { .err = SP_ERR_SYS_NOT_FOUND }, + }, { .name = "replaced", .target = TARGET_FILE, @@ -103,8 +112,6 @@ static const test_t tests [] = { }, }; -// Invalid by construction, not closed-then-reused: past any fd rlimit on -// POSIX, not a multiple of 4 on NT. No recycling race. #define GARBAGE_FD ((sp_sys_fd_t)0x55555) static bool same_file(sp_path_t a, sp_path_t b) { @@ -123,6 +130,7 @@ static sp_err_t run(sp_test_t* t, test_t* c) { sp_path_t path = { .dir = sandbox, .sub = sp_cstr_as_str(c->path) }; sp_path_t alias = { .dir = sandbox, .sub = sp_cstr_as_str(c->alias) }; + sp_path_t decoy = { .dir = sandbox, .sub = sp_cstr_as_str(c->decoy) }; sp_path_t other = { .dir = sandbox, .sub = sp_str_lit("B") }; sp_path_t expect = { .dir = sandbox, .sub = sp_cstr_as_str(c->expect.path) }; @@ -132,6 +140,7 @@ static sp_err_t run(sp_test_t* t, test_t* c) { case TARGET_FILE: { sp_must_ok(t, sp_fs_create_file_at(path)); if (c->alias) sp_must_ok(t, sp_fs_create_hard_link_at(path, alias)); + if (c->decoy) sp_must_ok(t, sp_fs_create_file_at(decoy)); sp_must_ok(t, sp_sys_open_s(path.dir, path.sub, SP_SYS_OPEN_MODE_RO, 0, &fd)); break; } From 7816fc540f22db35ee1745e1ad3f2661c4944193 Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Mon, 28 Sep 2026 18:58:59 -0400 Subject: [PATCH 42/72] fs: remove sp_rt.cwd --- sp.h | 3 --- test/fs/get_cwd.c | 25 ------------------------- test/fs/resolve.c | 20 -------------------- 3 files changed, 48 deletions(-) diff --git a/sp.h b/sp.h index cf3eff56..3db89653 100644 --- a/sp.h +++ b/sp.h @@ -4659,7 +4659,6 @@ typedef struct { typedef struct { const sp_sys_vtable_t* vt; - sp_sys_fd_t cwd; sp_os_signal_handler_t signal_handlers[3]; void* signal_userdata[3]; sp_spin_lock_t locks [SP_RT_NUM_SPIN_LOCKS]; @@ -5800,7 +5799,6 @@ const sp_sys_vtable_t sp_sys_vtable_platform = { sp_rt_t sp_rt = { .vt = &sp_sys_vtable_platform, - .cwd = SP_SYS_INVALID_FD, .err_str = sp_err_str, }; @@ -20245,7 +20243,6 @@ sp_str_t sp_fs_normalize_path(sp_mem_t mem, sp_str_t path) { } sp_sys_fd_t sp_fs_get_cwd() { - if (sp_rt.cwd != SP_SYS_INVALID_FD) return sp_rt.cwd; return sp_sys_get_root(0); } diff --git a/test/fs/get_cwd.c b/test/fs/get_cwd.c index 25ba4570..4f8f622c 100644 --- a/test/fs/get_cwd.c +++ b/test/fs/get_cwd.c @@ -39,31 +39,6 @@ sp_test(fs, get_cwd_is_root_zero) { return SP_OK; } -sp_test(fs, get_cwd_override, .serial = true) { - sp_test_skip_on_wasm() - - sp_sys_fd_t dir = SP_SYS_INVALID_FD; - sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sp_test_dir(t), 0, &dir)); - sp_must_ok(t, sp_fs_create_file_at((sp_path_t) { .dir = dir, .sub = sp_str_lit("A") })); - - sp_rt.cwd = dir; - sp_sys_fd_t cwd = sp_fs_get_cwd(); - sp_path_t path = sp_path_at_cwd(sp_str_lit("A")); - bool found = sp_fs_is_file(sp_str_lit("A")); - sp_str_t cwd_path = sp_fs_get_cwd_path(sp_test_arena(t)); - sp_rt.cwd = SP_SYS_INVALID_FD; - sp_sys_fd_t restored = sp_fs_get_cwd(); - sp_sys_close(dir); - - sp_expect_eq(t, cwd, dir); - sp_expect_eq(t, path.dir, dir); - sp_expect_str_eq(t, path.sub, sp_str_lit("A")); - sp_expect(t, found); - sp_expect_str_eq(t, cwd_path, sp_fs_canonicalize_path(sp_test_arena(t), sp_test_dir(t))); - sp_expect_eq(t, restored, sp_sys_get_root(0)); - return SP_OK; -} - sp_test(fs, path_at_cwd_passes_through) { sp_test_skip_on_wasm() diff --git a/test/fs/resolve.c b/test/fs/resolve.c index 097b2196..c5e5ec5e 100644 --- a/test/fs/resolve.c +++ b/test/fs/resolve.c @@ -3,11 +3,9 @@ #define MAX_ROOTS 4 #define ROOT_FD 9000 -#define CWD_FD 9100 typedef enum { DIR_ROOT, - DIR_CWD, DIR_NONE, } dir_t; @@ -25,7 +23,6 @@ typedef struct { typedef struct { const c8* name; root_t roots [MAX_ROOTS]; - bool cwd; const c8* path; expect_t expect; } test_t; @@ -68,13 +65,6 @@ static const test_t tests [] = { .path = "A/B", .expect = { .sub = "A/B" }, }, - { - .name = "relative_goes_to_the_cwd", - .roots = { { .label = "/A" } }, - .cwd = true, - .path = "A/B", - .expect = { .dir = DIR_CWD, .sub = "A/B" }, - }, { .name = "relative_ignores_relative_labels", .roots = { { .label = "." }, { .label = "A" } }, @@ -158,13 +148,6 @@ static const test_t tests [] = { .path = "/A/B", .expect = { .root = 1, .sub = "B" }, }, - { - .name = "unserved_goes_to_the_cwd", - .roots = { { .label = "/B" }, { .label = "/A" } }, - .cwd = true, - .path = "/C", - .expect = { .dir = DIR_CWD, .sub = "/C" }, - }, { .name = "unreadable_label_is_skipped", .roots = { { .err = SP_ERR_SYS_NAME_TOO_LONG }, { .label = "/A" } }, @@ -180,15 +163,12 @@ static sp_err_t run(sp_test_t* t, test_t* c) { active = c; const sp_sys_vtable_t* saved = sp_sys_set_vtable(&vt); - if (c->cwd) sp_rt.cwd = (sp_sys_fd_t)CWD_FD; sp_path_t path = sp_path_resolve(sp_cstr_as_str(c->path)); - sp_rt.cwd = SP_SYS_INVALID_FD; sp_sys_set_vtable(saved); active = SP_NULLPTR; switch (c->expect.dir) { case DIR_ROOT: sp_expect_eq(t, path.dir, (sp_sys_fd_t)(ROOT_FD + c->expect.root)); break; - case DIR_CWD: sp_expect_eq(t, path.dir, (sp_sys_fd_t)CWD_FD); break; case DIR_NONE: sp_expect_eq(t, path.dir, SP_SYS_INVALID_FD); break; } sp_expect_str_eq(t, path.sub, sp_cstr_as_str(c->expect.sub)); From 1f943f7411af956c41c84abb9f98094d4c16b508 Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Mon, 28 Sep 2026 19:00:38 -0400 Subject: [PATCH 43/72] fs: sp_path_join --- sp.h | 5 +++++ test/fs/path_join.c | 29 +++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 test/fs/path_join.c diff --git a/sp.h b/sp.h index 3db89653..853f5892 100644 --- a/sp.h +++ b/sp.h @@ -3123,6 +3123,7 @@ SP_API sp_err_t sp_fs_copy_tree_at(sp_path_t from, sp_path_t to, sp_ SP_API sp_sys_fd_t sp_fs_get_cwd(); SP_API sp_path_t sp_path_at_cwd(sp_str_t sub); SP_API sp_path_t sp_path_resolve(sp_str_t path); +SP_API sp_path_t sp_path_join(sp_mem_t mem, sp_path_t path, sp_str_t sub); // literally sys+normalize, if normalize isnt needed this isn't either? SP_API sp_str_t sp_fs_canonicalize_path(sp_mem_t mem, sp_str_t path); @@ -20284,6 +20285,10 @@ sp_path_t sp_path_resolve(sp_str_t path) { } } +sp_path_t sp_path_join(sp_mem_t mem, sp_path_t path, sp_str_t sub) { + return (sp_path_t) { .dir = path.dir, .sub = sp_fs_join_path(mem, path.sub, sub) }; +} + sp_fs_kind_t sp_fs_get_link_kind_at(sp_path_t path) { if (sp_str_empty(path.sub)) return SP_FS_KIND_NONE; sp_sys_file_meta_t st = sp_zero; diff --git a/test/fs/path_join.c b/test/fs/path_join.c new file mode 100644 index 00000000..74e0bd0d --- /dev/null +++ b/test/fs/path_join.c @@ -0,0 +1,29 @@ +#include "sp.h" +#include "sp/sp_test.h" + +#define DIR_FD 9000 + +typedef struct { + const c8* name; + const c8* a; + const c8* b; + const c8* expect; +} test_t; + +static const test_t tests [] = { + { .name = "basic", .a = "A", .b = "B", .expect = "A/B" }, + { .name = "nested_rhs", .a = "A", .b = "B/C", .expect = "A/B/C" }, + { .name = "dot_lhs", .a = ".", .b = "A", .expect = "./A" }, + { .name = "trailing_lhs", .a = "A/", .b = "B", .expect = "A/B" }, + { .name = "leading_rhs", .a = "A", .b = "/B", .expect = "A/B" }, + { .name = "empty_lhs", .a = "", .b = "A", .expect = "A" }, + { .name = "empty_rhs", .a = "A", .b = "", .expect = "A" }, +}; + +sp_test_each(fs, path_join, test_t, tests) { + sp_path_t path = { .dir = (sp_sys_fd_t)DIR_FD, .sub = sp_cstr_as_str(it->a) }; + sp_path_t result = sp_path_join(sp_test_arena(t), path, sp_cstr_as_str(it->b)); + sp_expect_eq(t, result.dir, (sp_sys_fd_t)DIR_FD); + sp_expect_str_eq_c(t, result.sub, it->expect); + return SP_OK; +} From f2ebcf35e8487e28301bd3293abaddd28c4e87b7 Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Mon, 28 Sep 2026 19:01:18 -0400 Subject: [PATCH 44/72] fs: sp_fs_for takes an sp_path_t --- sp.h | 8 ++++---- tools/sp.c | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/sp.h b/sp.h index 853f5892..92148b1c 100644 --- a/sp.h +++ b/sp.h @@ -3179,10 +3179,10 @@ typedef struct { sp_err_t err; } sp_fs_it_t; -#define sp_fs_for(mem, dir, it) \ - for (sp_fs_it_t it = sp_fs_it_new(mem, dir); sp_fs_it_next(&it);) -#define sp_fs_for_recursive(mem, dir, it) \ - for (sp_fs_it_t it = sp_fs_it_new(mem, dir); sp_fs_it_walk(&it);) +#define sp_fs_for(mem, path, it) \ + for (sp_fs_it_t it = sp_fs_it_new_at(mem, path, 0); sp_fs_it_next(&it);) +#define sp_fs_for_recursive(mem, path, it) \ + for (sp_fs_it_t it = sp_fs_it_new_at(mem, path, 0); sp_fs_it_walk(&it);) SP_API sp_err_t sp_fs_dir_open(sp_fs_dir_t* it, sp_sys_fd_t fd, sp_str_t path, u32 flags, sp_mem_slice_t buf); SP_API sp_err_t sp_fs_dir_next(sp_fs_dir_t* it, sp_fs_dir_entry_t* out); diff --git a/tools/sp.c b/tools/sp.c index 99fa8b32..e61fa16b 100644 --- a/tools/sp.c +++ b/tools/sp.c @@ -160,7 +160,7 @@ static sp_da(const target_t*) target_resolve(sp_mem_t mem, sp_str_t query) { static sp_da(sp_str_t) tests_discover(sp_mem_t mem, sp_str_t dir, sp_str_t ext) { sp_da(sp_str_t) names = sp_da_new(mem, sp_str_t); - sp_fs_for(mem, dir, it) { + sp_fs_for(mem, sp_path_resolve(dir), it) { sp_str_t name = it.entry.name; if (!sp_str_empty(ext)) { if (!sp_str_ends_with(name, ext)) continue; From 44528bc929f1e285a8dce4e903be9127fb44cd8d Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Mon, 28 Sep 2026 19:03:01 -0400 Subject: [PATCH 45/72] cli: completer joins with sp_fs_join_path --- sp/sp_cli.h | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/sp/sp_cli.h b/sp/sp_cli.h index 1ff43287..43d206bc 100644 --- a/sp/sp_cli.h +++ b/sp/sp_cli.h @@ -1385,10 +1385,8 @@ SP_PRIVATE sp_str_t sp_cli_completer_path(sp_cli_desc_t desc, c8* buffer, u32 le if (sp_sys_get_fd_path(sp_fs_get_cwd(), cwd, sizeof(cwd), &cwd_len)) return arg0; if (sp_cast(u32, cwd_len) + 1 + arg0.len > len) return arg0; - sp_io_mem_writer_t path = sp_zero; - sp_io_mem_writer_from_buffer(&path, buffer, len); - sp_fmt_io(&path.base, "{}/{}", sp_fmt_str(sp_str(cwd, sp_cast(u32, cwd_len))), sp_fmt_str(arg0)); - return sp_io_mem_writer_as_str(&path); + sp_mem_fixed_t fixed = sp_mem_fixed_ex(buffer, len, 1); + return sp_fs_join_path(sp_mem_fixed_as_allocator(&fixed), sp_str(cwd, sp_cast(u32, cwd_len)), arg0); } void sp_cli_write_completions(sp_io_writer_t* io, sp_cli_desc_t desc, sp_cli_shell_t shell) { From 2df633c7219497334b76414ac8ef96197299e83e Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Mon, 28 Sep 2026 19:05:06 -0400 Subject: [PATCH 46/72] sys: sp_err_t for exe/storage/config paths and lseek --- sp.h | 177 ++++++++++++++++++++++++------------------- test/io/file.c | 6 +- test/ps.c | 10 +-- test/sys/self_path.c | 6 +- 4 files changed, 113 insertions(+), 86 deletions(-) diff --git a/sp.h b/sp.h index 92148b1c..dd31f29e 100644 --- a/sp.h +++ b/sp.h @@ -1457,9 +1457,9 @@ SP_API sp_err_t sp_sys_transfer(sp_sys_fd_t in, u64* in_pos, sp_sys_fd_t out, SP_API sp_err_t sp_sys_transfer_positional(sp_sys_fd_t in, u64* in_pos, sp_sys_fd_t out, u64 count, u64 offset, u64* bytes_moved); SP_API sp_sys_fd_t sp_sys_get_root(s32 it); SP_API sp_err_t sp_sys_get_root_label(s32 it, c8* buf, u64 size, u64* len); -SP_API s64 sp_sys_get_exe_path(c8* buf, u64 size); -SP_API s64 sp_sys_get_storage_path(c8* buf, u64 size); -SP_API s64 sp_sys_get_config_path(c8* buf, u64 size); +SP_API sp_err_t sp_sys_get_exe_path(c8* buf, u64 size, u64* len); +SP_API sp_err_t sp_sys_get_storage_path(c8* buf, u64 size, u64* len); +SP_API sp_err_t sp_sys_get_config_path(c8* buf, u64 size, u64* len); SP_API sp_err_t sp_sys_get_fd_path(sp_sys_fd_t fd, c8* buf, u64 size, u64* len); SP_API sp_err_t sp_sys_open(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_open_mode_t mode, u32 flags, sp_sys_fd_t* out); SP_API sp_err_t sp_sys_open_dir(sp_sys_fd_t fd, const c8* path, u32 len, u32 flags, sp_sys_fd_t* out); @@ -1517,7 +1517,7 @@ SP_API void sp_sys_assert(bool cond); SP_API void sp_sys_exit(s32 code); SP_API void sp_sys_env(const c8** env, u32* len); -SP_API s64 sp_sys_lseek(sp_sys_fd_t fd, s64 offset, s32 whence); +SP_API sp_err_t sp_sys_lseek(sp_sys_fd_t fd, s64 offset, s32 whence, s64* position); SP_API sp_err_t sp_sys_chdir(const c8* path, u32 len); @@ -1556,9 +1556,9 @@ typedef struct { sp_err_t (*transfer_positional)(sp_sys_fd_t in, u64* in_pos, sp_sys_fd_t out, u64 count, u64 offset, u64* bytes_moved); sp_sys_fd_t (*get_root)(s32 it); sp_err_t (*get_root_label)(s32 it, c8* buf, u64 size, u64* len); - s64 (*get_exe_path)(c8* buf, u64 size); - s64 (*get_storage_path)(c8* buf, u64 size); - s64 (*get_config_path)(c8* buf, u64 size); + sp_err_t (*get_exe_path)(c8* buf, u64 size, u64* len); + sp_err_t (*get_storage_path)(c8* buf, u64 size, u64* len); + sp_err_t (*get_config_path)(c8* buf, u64 size, u64* len); sp_err_t (*get_fd_path)(sp_sys_fd_t fd, c8* buf, u64 size, u64* len); sp_err_t (*open)(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_open_mode_t mode, u32 flags, sp_sys_fd_t* out); sp_err_t (*open_dir)(sp_sys_fd_t fd, const c8* path, u32 len, u32 flags, sp_sys_fd_t*); @@ -1615,7 +1615,7 @@ typedef struct { void (*assert)(bool cond); void (*exit)(s32 code); void (*env)(const c8** env, u32* len); - s64 (*lseek)(sp_sys_fd_t fd, s64 offset, s32 whence); + sp_err_t (*lseek)(sp_sys_fd_t fd, s64 offset, s32 whence, s64* position); sp_err_t (*chdir)(const c8* path, u32 len); sp_err_t (*dir_it_open)(sp_sys_fd_t fd, sp_sys_dir_it_t* out); sp_err_t (*dir_it_read)(sp_sys_dir_it_t* it, sp_mem_buffer_t* buf); @@ -1631,9 +1631,9 @@ SP_API sp_err_t sp_sys_transfer_p(sp_sys_fd_t in, u64* in_pos, sp_sys_fd_t ou SP_API sp_err_t sp_sys_transfer_positional_p(sp_sys_fd_t in, u64* in_pos, sp_sys_fd_t out, u64 count, u64 offset, u64* bytes_moved); SP_API sp_sys_fd_t sp_sys_get_root_p(s32 it); SP_API sp_err_t sp_sys_get_root_label_p(s32 it, c8* buf, u64 size, u64* len); -SP_API s64 sp_sys_get_exe_path_p(c8* buf, u64 size); -SP_API s64 sp_sys_get_storage_path_p(c8* buf, u64 size); -SP_API s64 sp_sys_get_config_path_p(c8* buf, u64 size); +SP_API sp_err_t sp_sys_get_exe_path_p(c8* buf, u64 size, u64* len); +SP_API sp_err_t sp_sys_get_storage_path_p(c8* buf, u64 size, u64* len); +SP_API sp_err_t sp_sys_get_config_path_p(c8* buf, u64 size, u64* len); SP_API sp_err_t sp_sys_get_fd_path_p(sp_sys_fd_t fd, c8* buf, u64 size, u64* len); SP_API sp_err_t sp_sys_open_p(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_open_mode_t mode, u32 flags, sp_sys_fd_t* out); SP_API sp_err_t sp_sys_open_dir_p(sp_sys_fd_t fd, const c8* path, u32 len, u32 flags, sp_sys_fd_t* out); @@ -1690,7 +1690,7 @@ SP_API s32 sp_sys_memcmp_p(const void* a, const void* b, u64 n); SP_API void sp_sys_assert_p(bool cond); SP_API void sp_sys_exit_p(s32 code); SP_API void sp_sys_env_p(const c8** env, u32* len); -SP_API s64 sp_sys_lseek_p(sp_sys_fd_t fd, s64 offset, s32 whence); +SP_API sp_err_t sp_sys_lseek_p(sp_sys_fd_t fd, s64 offset, s32 whence, s64* position); SP_API sp_err_t sp_sys_chdir_p(const c8* path, u32 len); SP_API sp_err_t sp_sys_dir_it_open_p(sp_sys_fd_t fd, sp_sys_dir_it_t* out); SP_API sp_err_t sp_sys_dir_it_read_p(sp_sys_dir_it_t* it, sp_mem_buffer_t* buf); @@ -5908,16 +5908,16 @@ sp_err_t sp_sys_get_root_label(s32 it, c8* buf, u64 size, u64* len) { return (sp_rt.vt->get_root_label)(it, buf, size, len); } -s64 sp_sys_get_exe_path(c8* buf, u64 size) { - return (sp_rt.vt->get_exe_path)(buf, size); +sp_err_t sp_sys_get_exe_path(c8* buf, u64 size, u64* len) { + return (sp_rt.vt->get_exe_path)(buf, size, len); } -s64 sp_sys_get_storage_path(c8* buf, u64 size) { - return (sp_rt.vt->get_storage_path)(buf, size); +sp_err_t sp_sys_get_storage_path(c8* buf, u64 size, u64* len) { + return (sp_rt.vt->get_storage_path)(buf, size, len); } -s64 sp_sys_get_config_path(c8* buf, u64 size) { - return (sp_rt.vt->get_config_path)(buf, size); +sp_err_t sp_sys_get_config_path(c8* buf, u64 size, u64* len) { + return (sp_rt.vt->get_config_path)(buf, size, len); } sp_err_t sp_sys_get_fd_path(sp_sys_fd_t fd, c8* buf, u64 size, u64* len) { @@ -6172,8 +6172,8 @@ void sp_sys_env(const c8** env, u32* len) { (sp_rt.vt->env)(env, len); } -s64 sp_sys_lseek(sp_sys_fd_t fd, s64 offset, s32 whence) { - return (sp_rt.vt->lseek)(fd, offset, whence); +sp_err_t sp_sys_lseek(sp_sys_fd_t fd, s64 offset, s32 whence, s64* position) { + return (sp_rt.vt->lseek)(fd, offset, whence, position); } sp_err_t sp_sys_chdir(const c8* path, u32 len) { @@ -8354,19 +8354,22 @@ sp_err_t sp_sys_transfer_positional_p(sp_sys_fd_t in, u64* in_pos, sp_sys_fd_t o ////////////////// // SP_SYS_LSEEK // ////////////////// -s64 sp_sys_lseek_p(sp_sys_fd_t fd, s64 offset, s32 whence) { +sp_err_t sp_sys_lseek_p(sp_sys_fd_t fd, s64 offset, s32 whence, s64* position) { + if (position) *position = 0; + #if defined(SP_WIN32) DWORD method; switch (whence) { case SP_IO_SEEK_SET: method = FILE_BEGIN; break; case SP_IO_SEEK_CUR: method = FILE_CURRENT; break; case SP_IO_SEEK_END: method = FILE_END; break; - default: return -1; + default: return SP_ERR_SYS_INVALID; } LARGE_INTEGER dist = { .QuadPart = offset }; LARGE_INTEGER new_pos; - if (!SetFilePointerEx((HANDLE)fd, dist, &new_pos, method)) return -1; - return (s64)new_pos.QuadPart; + if (!SetFilePointerEx((HANDLE)fd, dist, &new_pos, method)) return sp_sys_err_from_win32(GetLastError()); + if (position) *position = (s64)new_pos.QuadPart; + return SP_OK; #elif defined(SP_LINUX) s32 native; @@ -8374,10 +8377,12 @@ s64 sp_sys_lseek_p(sp_sys_fd_t fd, s64 offset, s32 whence) { case SP_IO_SEEK_SET: native = 0; break; case SP_IO_SEEK_CUR: native = 1; break; case SP_IO_SEEK_END: native = 2; break; - default: return -1; + default: return SP_ERR_SYS_INVALID; } s64 rc = sp_syscall(SP_SYSCALL_NUM_LSEEK, fd, offset, native); - return rc < 0 ? -1 : rc; + if (rc < 0) return sp_sys_err_from_errno(-rc); + if (position) *position = rc; + return SP_OK; #elif defined(SP_MACOS) || defined(SP_COSMO) s32 native; @@ -8385,9 +8390,12 @@ s64 sp_sys_lseek_p(sp_sys_fd_t fd, s64 offset, s32 whence) { case SP_IO_SEEK_SET: native = SEEK_SET; break; case SP_IO_SEEK_CUR: native = SEEK_CUR; break; case SP_IO_SEEK_END: native = SEEK_END; break; - default: return -1; + default: return SP_ERR_SYS_INVALID; } - return lseek(fd, offset, native); + s64 rc = lseek(fd, offset, native); + if (rc < 0) return sp_sys_err_from_errno(errno); + if (position) *position = rc; + return SP_OK; #elif defined(SP_WASM) __wasi_whence_t native; @@ -8395,11 +8403,12 @@ s64 sp_sys_lseek_p(sp_sys_fd_t fd, s64 offset, s32 whence) { case SP_IO_SEEK_SET: native = __WASI_WHENCE_SET; break; case SP_IO_SEEK_CUR: native = __WASI_WHENCE_CUR; break; case SP_IO_SEEK_END: native = __WASI_WHENCE_END; break; - default: return -1; + default: return SP_ERR_SYS_INVALID; } __wasi_filesize_t newoff = 0; - __wasi_errno_t err = __wasi_fd_seek((__wasi_fd_t)fd, (__wasi_filedelta_t)offset, native, &newoff); - return err ? -1 : (s64)newoff; + sp_try(sp_sys_err_from_wasi(__wasi_fd_seek((__wasi_fd_t)fd, (__wasi_filedelta_t)offset, native, &newoff))); + if (position) *position = (s64)newoff; + return SP_OK; #else #error "sp_sys_lseek" @@ -8905,21 +8914,25 @@ void sp_sys_futex_wake_all_p(u32* addr) { } #if defined(SP_WIN32) -static s64 sp_sys_copy_env_var(const c8* name, c8* buf, u64 size) { - DWORD len = GetEnvironmentVariableA(name, buf, (DWORD)size); - if (len == 0 || len >= size) return -1; - return (s64)len; +static sp_err_t sp_sys_copy_env_var(const c8* name, c8* buf, u64 size, u64* len) { + *len = 0; + DWORD n = GetEnvironmentVariableA(name, buf, (DWORD)size); + if (n == 0) return SP_ERR_SYS_NOT_FOUND; + if (n >= size) return SP_ERR_SYS_NAME_TOO_LONG; + *len = n; + return SP_OK; } -s64 sp_sys_get_storage_path_p(c8* buf, u64 size) { - return sp_sys_copy_env_var("LOCALAPPDATA", buf, size); +sp_err_t sp_sys_get_storage_path_p(c8* buf, u64 size, u64* len) { + return sp_sys_copy_env_var("LOCALAPPDATA", buf, size, len); } -s64 sp_sys_get_config_path_p(c8* buf, u64 size) { - return sp_sys_copy_env_var("APPDATA", buf, size); +sp_err_t sp_sys_get_config_path_p(c8* buf, u64 size, u64* len) { + return sp_sys_copy_env_var("APPDATA", buf, size, len); } #else -static s64 sp_sys_xdg_or_home(sp_str_t xdg_var, sp_str_t home_suffix, c8* buf, u64 size) { +static sp_err_t sp_sys_xdg_or_home(sp_str_t xdg_var, sp_str_t home_suffix, c8* buf, u64 size, u64* len) { + *len = 0; sp_mem_arena_marker_t scratch = sp_mem_begin_scratch(); sp_str_t value = sp_os_env_get(xdg_var); @@ -8930,23 +8943,29 @@ static s64 sp_sys_xdg_or_home(sp_str_t xdg_var, sp_str_t home_suffix, c8* buf, u } } - s64 result = -1; - if (!sp_str_empty(value) && value.len < size) { - for (u32 i = 0; i < value.len; i++) buf[i] = value.data[i]; - buf[value.len] = '\0'; - result = (s64)value.len; + sp_err_t err = SP_OK; + if (sp_str_empty(value)) { + err = SP_ERR_SYS_NOT_FOUND; + } + else if (value.len >= size) { + err = SP_ERR_SYS_NAME_TOO_LONG; + } + else { + sp_mem_copy(buf, value.data, value.len); + buf[value.len] = 0; + *len = value.len; } sp_mem_end_scratch(scratch); - return result; + return err; } -s64 sp_sys_get_storage_path_p(c8* buf, u64 size) { - return sp_sys_xdg_or_home(sp_str_lit("XDG_DATA_HOME"), sp_str_lit(".local/share"), buf, size); +sp_err_t sp_sys_get_storage_path_p(c8* buf, u64 size, u64* len) { + return sp_sys_xdg_or_home(sp_str_lit("XDG_DATA_HOME"), sp_str_lit(".local/share"), buf, size, len); } -s64 sp_sys_get_config_path_p(c8* buf, u64 size) { - return sp_sys_xdg_or_home(sp_str_lit("XDG_CONFIG_HOME"), sp_str_lit(".config"), buf, size); +sp_err_t sp_sys_get_config_path_p(c8* buf, u64 size, u64* len) { + return sp_sys_xdg_or_home(sp_str_lit("XDG_CONFIG_HOME"), sp_str_lit(".config"), buf, size, len); } #endif @@ -11202,55 +11221,61 @@ sp_err_t sp_sys_get_fd_path_p(sp_sys_fd_t fd, c8* buf, u64 size, u64* len) { ///////////////////////// // SP_SYS_GET_EXE_PATH // ///////////////////////// -s64 sp_sys_get_exe_path_p(c8* buf, u64 size) { -#if defined(SP_WIN32) - if (!buf) return -1; +sp_err_t sp_sys_get_exe_path_p(c8* buf, u64 size, u64* len) { + *len = 0; +#if defined(SP_WIN32) sp_nt_unicode_string_t* image = (sp_nt_unicode_string_t*)(sp_nt_process_params() + 0x60); u32 wlen = image->Length / (u32)sizeof(u16); SP_ALIGNED c8 u8buf[SP_PATH_MAX * 3 + 1]; sp_mem_fixed_t fixed = sp_mem_fixed(u8buf, sizeof(u8buf)); sp_str_t utf8; - if (sp_wtf16_to_wtf8(sp_mem_fixed_as_allocator(&fixed), (sp_wide_str_t) { .data = image->Buffer, .len = wlen }, &utf8) != SP_OK) return -1; - return sp_sys_win32_copy_path(utf8, buf, size); + sp_try(sp_wtf16_to_wtf8(sp_mem_fixed_as_allocator(&fixed), (sp_wide_str_t) { .data = image->Buffer, .len = wlen }, &utf8)); + + s64 n = sp_sys_win32_copy_path(utf8, buf, size); + if (n < 0) return SP_ERR_SYS_NAME_TOO_LONG; + *len = (u64)n; + return SP_OK; #elif defined(SP_LINUX) c8 rbuf [SP_PATH_MAX]; s64 n = sp_syscall(SP_SYSCALL_NUM_READLINKAT, SP_AT_FDCWD, "/proc/self/exe", rbuf, SP_PATH_MAX); - if (n < 0 || n == SP_PATH_MAX || (u64)n >= size) return -1; + if (n < 0) return sp_sys_err_from_errno(-n); + if (n == SP_PATH_MAX || (u64)n >= size) return SP_ERR_SYS_NAME_TOO_LONG; sp_mem_copy(buf, rbuf, (u64)n); buf[n] = 0; - return n; + *len = (u64)n; + return SP_OK; #elif defined(SP_MACOS) - if (!buf || size == 0) return -1; c8 raw[4096]; u32 raw_size = sizeof(raw); - if (_NSGetExecutablePath(raw, &raw_size)) return -1; + if (_NSGetExecutablePath(raw, &raw_size)) return SP_ERR_SYS_NAME_TOO_LONG; c8 resolved [SP_PATH_MAX]; - if (!realpath(raw, resolved)) return -1; + if (!realpath(raw, resolved)) return sp_sys_err_from_errno(errno); u64 n = sp_cstr_len(resolved); - if (n >= size) return -1; + if (n >= size) return SP_ERR_SYS_NAME_TOO_LONG; sp_mem_copy(buf, resolved, n); buf[n] = 0; - return (s64)n; + *len = n; + return SP_OK; #elif defined(SP_COSMO) - if (!buf || size == 0) return -1; extern char* program_invocation_name; c8 resolved [SP_PATH_MAX]; - if (!realpath(program_invocation_name, resolved)) return -1; + if (!realpath(program_invocation_name, resolved)) return sp_sys_err_from_errno(errno); u64 n = sp_cstr_len(resolved); - if (n >= size) return -1; + if (n >= size) return SP_ERR_SYS_NAME_TOO_LONG; sp_mem_copy(buf, resolved, n); buf[n] = 0; - return (s64)n; + *len = n; + return SP_OK; #elif defined(SP_WASM) (void)buf; (void)size; - return -1; + return SP_ERR_SYS_UNSUPPORTED; #else #error "sp_sys_get_exe_path" @@ -20406,9 +20431,9 @@ sp_str_t sp_fs_canonicalize_path(sp_mem_t mem, sp_str_t path) { sp_str_t sp_fs_get_exe_path(sp_mem_t mem) { c8 buf[SP_PATH_MAX]; - s64 len = sp_sys_get_exe_path(buf, SP_PATH_MAX); - if (len <= 0) return sp_str_lit(""); - return sp_str_copy(mem, sp_str(buf, len)); + u64 len = 0; + if (sp_sys_get_exe_path(buf, SP_PATH_MAX, &len)) return sp_zero_s(sp_str_t); + return sp_str_copy(mem, sp_str(buf, (u32)len)); } sp_str_t sp_fs_get_cwd_path(sp_mem_t mem) { @@ -20420,16 +20445,16 @@ sp_str_t sp_fs_get_cwd_path(sp_mem_t mem) { sp_str_t sp_fs_get_storage_path(sp_mem_t mem) { c8 buf[SP_PATH_MAX]; - s64 len = sp_sys_get_storage_path(buf, SP_PATH_MAX); - if (len <= 0) return sp_zero_s(sp_str_t); - return sp_fs_normalize_path(mem, sp_str(buf, len)); + u64 len = 0; + if (sp_sys_get_storage_path(buf, SP_PATH_MAX, &len)) return sp_zero_s(sp_str_t); + return sp_fs_normalize_path(mem, sp_str(buf, (u32)len)); } sp_str_t sp_fs_get_config_path(sp_mem_t mem) { c8 buf[SP_PATH_MAX]; - s64 len = sp_sys_get_config_path(buf, SP_PATH_MAX); - if (len <= 0) return sp_zero_s(sp_str_t); - return sp_fs_normalize_path(mem, sp_str(buf, len)); + u64 len = 0; + if (sp_sys_get_config_path(buf, SP_PATH_MAX, &len)) return sp_zero_s(sp_str_t); + return sp_fs_normalize_path(mem, sp_str(buf, (u32)len)); } sp_err_t sp_io_read_file_at(sp_mem_t mem, sp_path_t path, sp_str_t* content) { diff --git a/test/io/file.c b/test/io/file.c index 36c6c240..92d3b748 100644 --- a/test/io/file.c +++ b/test/io/file.c @@ -579,14 +579,16 @@ UTEST_F(io, file_reader_positional_does_not_touch_kernel_cursor) { sp_sys_fd_t fd = SP_SYS_INVALID_FD; sp_sys_open_s(ut.file.dir, ut.file.sub, SP_SYS_OPEN_MODE_RO, 0, &fd); const s64 parked = 7; - sp_sys_lseek(fd, parked, SP_IO_SEEK_SET); + sp_sys_lseek(fd, parked, SP_IO_SEEK_SET, SP_NULLPTR); sp_io_file_reader_t r = sp_zero; sp_io_file_reader_from_file(&r, fd, SP_IO_CLOSE_MODE_NONE); u8 buf [4] = sp_zero; sp_io_read(&r.base, buf, 4, SP_NULLPTR); - EXPECT_EQ(sp_sys_lseek(fd, 0, SP_IO_SEEK_CUR), parked); + s64 position = 0; + sp_sys_lseek(fd, 0, SP_IO_SEEK_CUR, &position); + EXPECT_EQ(position, parked); sp_io_file_reader_close(&r); sp_sys_close(fd); diff --git a/test/ps.c b/test/ps.c index 8638771a..76dec2e9 100644 --- a/test/ps.c +++ b/test/ps.c @@ -311,7 +311,7 @@ UTEST_F(ps, io_create_file_null) { }, .fn = TEST_PROC_FUNCTION_ECHO, }); - sp_sys_lseek(fd, 0, SP_SEEK_SET); + sp_sys_lseek(fd, 0, SP_SEEK_SET, SP_NULLPTR); u64 bytes_read = 0; sp_sys_read(fd, ut.buffer.data, ut.buffer.len, &bytes_read); @@ -327,7 +327,7 @@ UTEST_F(ps, io_file_create_null) { sp_sys_fd_t fd = SP_SYS_INVALID_FD; sp_sys_open_s(sp_sys_get_root(0), file_path, SP_SYS_OPEN_MODE_RW, SP_SYS_OPEN_CREATE, &fd); sp_sys_write(fd, sp_test_ps_canary.data, sp_test_ps_canary.len, SP_NULLPTR); - sp_sys_lseek(fd, 0, SP_SEEK_SET); + sp_sys_lseek(fd, 0, SP_SEEK_SET, SP_NULLPTR); sp_test_proc_io(&ut, &ur, (sp_test_proc_io_config_t) { .io = { @@ -366,7 +366,7 @@ UTEST_F(ps, io_create_null_file) { }, .fn = TEST_PROC_FUNCTION_ECHO, }); - sp_sys_lseek(fd, 0, SP_SEEK_SET); + sp_sys_lseek(fd, 0, SP_SEEK_SET, SP_NULLPTR); u64 bytes_read = 0; sp_sys_read(fd, ut.buffer.data, ut.buffer.len, &bytes_read); @@ -382,7 +382,7 @@ UTEST_F(ps, io_file_null_file) { sp_sys_fd_t in_fd = SP_SYS_INVALID_FD; sp_sys_open_s(sp_sys_get_root(0), in_path, SP_SYS_OPEN_MODE_RW, SP_SYS_OPEN_CREATE, &in_fd); sp_sys_write(in_fd, sp_test_ps_canary.data, sp_test_ps_canary.len, SP_NULLPTR); - sp_sys_lseek(in_fd, 0, SP_SEEK_SET); + sp_sys_lseek(in_fd, 0, SP_SEEK_SET, SP_NULLPTR); sp_str_t err_path = sp_test_file_create_empty(&ut.file_manager, sp_str_lit("stderr.file")); sp_sys_fd_t err_fd = SP_SYS_INVALID_FD; @@ -401,7 +401,7 @@ UTEST_F(ps, io_file_null_file) { }, .fn = TEST_PROC_FUNCTION_ECHO, }); - sp_sys_lseek(err_fd, 0, SP_SEEK_SET); + sp_sys_lseek(err_fd, 0, SP_SEEK_SET, SP_NULLPTR); u64 bytes_read = 0; sp_sys_read(err_fd, ut.buffer.data, ut.buffer.len, &bytes_read); diff --git a/test/sys/self_path.c b/test/sys/self_path.c index 27425644..36fe8b4a 100644 --- a/test/sys/self_path.c +++ b/test/sys/self_path.c @@ -29,9 +29,9 @@ static sp_err_t run(sp_test_t* t, test_t* c) { break; } case PATH_EXE: { - s64 rc = sp_sys_get_exe_path(buf, sizeof(buf)); - if (rc <= 0) return sp_test_skip(t, "not available"); - n = (u64)rc; + sp_err_t err = sp_sys_get_exe_path(buf, sizeof(buf), &n); + if (err == SP_ERR_SYS_UNSUPPORTED) return sp_test_skip(t, "not available"); + sp_must_ok(t, err); break; } } From 671b955f42d585b829ddea3fd09343b49670d7ca Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Tue, 29 Sep 2026 00:22:48 -0400 Subject: [PATCH 47/72] test: symlink targets are relative to the dir now --- sp/sp_test.h | 20 +++++++ test/fs/canonicalize.c | 2 +- test/fs/copy.c | 18 +++--- test/fs/create_dir.c | 2 +- test/fs/dir/os.c | 2 +- test/fs/fs.h | 68 ++++------------------- test/fs/it/enter.c | 4 +- test/fs/it/os.c | 4 +- test/fs/links.c | 122 +++++++++++++++++++++-------------------- test/fs/predicates.c | 2 +- test/fs/remove/os.c | 4 +- test/sys/harness.h | 2 - test/sys/path.c | 17 ++---- test/sys/readlink.c | 10 ++-- test/sys/sys.c | 25 +-------- test/sys/sys.h | 2 +- 16 files changed, 127 insertions(+), 177 deletions(-) diff --git a/sp/sp_test.h b/sp/sp_test.h index 69b877b8..bea43772 100644 --- a/sp/sp_test.h +++ b/sp/sp_test.h @@ -369,6 +369,8 @@ typedef struct { #define sp_test_skip_on_linux() #endif +#define sp_test_skip_without_symlinks() if (!sp_test_symlinks_supported(t)) return sp_test_skip(t, "symlinks not available"); + SP_API sp_err_t sp_test_skip(sp_test_t* t, const c8* fmt, ...); SP_API void sp_test_fail(sp_test_t* t, const c8* fmt, ...); SP_API sp_str_t sp_test_get_name(sp_test_t* t); @@ -408,6 +410,8 @@ typedef struct { SP_TYPEDEF_FN(sp_err_t, sp_test_once_fn_t, void* user); SP_API sp_err_t sp_test_once(sp_test_once_t* once, sp_test_once_fn_t fn, void* user); +SP_API bool sp_test_symlinks_supported(sp_test_t* t); + typedef enum { SP_TEST_ALLOC_LIVE, @@ -734,6 +738,7 @@ struct sp_test_runner_t { sp_da(const c8*) skipped; sp_da(const c8*) updated; sp_str_t dir_root; + sp_test_once_t symlinks; sp_str_t golden_root; bool update; sp_test_keep_t keep; @@ -1036,6 +1041,21 @@ sp_err_t sp_test_once(sp_test_once_t* once, sp_test_once_fn_t fn, void* user) { return once->err; } +static sp_err_t sp_test_probe_symlinks(void* user) { + sp_test_runner_t* runner = (sp_test_runner_t*)user; + sp_mem_arena_marker_t s = sp_mem_begin_scratch(); + sp_str_t link = sp_fs_join_path(s.mem, runner->dir_root, sp_str_lit("L")); + sp_err_t err = sp_fs_create_dir(runner->dir_root); + if (!err) err = sp_fs_create_sym_link(sp_str_lit("A"), link); + if (!err) sp_fs_remove_file(link); + sp_mem_end_scratch(s); + return err; +} + +bool sp_test_symlinks_supported(sp_test_t* t) { + return !sp_test_once(&t->runner->symlinks, sp_test_probe_symlinks, t->runner); +} + //////////// // GOLDEN // diff --git a/test/fs/canonicalize.c b/test/fs/canonicalize.c index a6346351..35b4f7a1 100644 --- a/test/fs/canonicalize.c +++ b/test/fs/canonicalize.c @@ -158,7 +158,7 @@ static const test_t tests [] = { sp_test_each(fs, canonicalize, test_t, tests) { sp_test_skip_on_wasm() - skip_if_symlinks_needed(t, it->setup); + if (fs_setup_needs_symlinks(it->setup)) sp_test_skip_without_symlinks(); sp_mem_t mem = sp_test_arena(t); sp_str_t sandbox = sp_test_dir(t); diff --git a/test/fs/copy.c b/test/fs/copy.c index d2890094..c1814443 100644 --- a/test/fs/copy.c +++ b/test/fs/copy.c @@ -349,14 +349,14 @@ static const test_t tests [] = { .setup = { { "A", FS_SETUP_DIR }, { .path = "A/C", .content = "C" }, - { .path = "A/L", .kind = FS_SETUP_SYMLINK, .target = "A/C" }, + { .path = "A/L", .kind = FS_SETUP_SYMLINK, .target = "C" }, }, .op = OP_COPY_TREE, .src = "A", .dst = "E", .expect = { { .path = "E/C", .exists = true, .kind = SP_FS_KIND_FILE, .content = "C" }, - { .path = "E/L", .exists = true, .kind = SP_FS_KIND_SYMLINK, .target = "A/C" }, + { .path = "E/L", .exists = true, .kind = SP_FS_KIND_SYMLINK, .target = "C" }, }, }, { @@ -364,15 +364,15 @@ static const test_t tests [] = { .setup = { { "A", FS_SETUP_DIR }, { .path = "A/C", .content = "C" }, - { .path = "A/L", .kind = FS_SETUP_SYMLINK, .target = "A/C" }, + { .path = "A/L", .kind = FS_SETUP_SYMLINK, .target = "C" }, { "E", FS_SETUP_DIR }, - { .path = "E/L", .kind = FS_SETUP_SYMLINK, .target = "A" }, + { .path = "E/L", .kind = FS_SETUP_SYMLINK, .target = "../A" }, }, .op = OP_COPY_TREE, .src = "A", .dst = "E", .expect = { - { .path = "E/L", .exists = true, .kind = SP_FS_KIND_SYMLINK, .target = "A/C" }, + { .path = "E/L", .exists = true, .kind = SP_FS_KIND_SYMLINK, .target = "C" }, }, }, { @@ -380,9 +380,9 @@ static const test_t tests [] = { .setup = { { "A", FS_SETUP_DIR }, { .path = "A/C", .content = "C" }, - { .path = "A/L", .kind = FS_SETUP_SYMLINK, .target = "A/C" }, + { .path = "A/L", .kind = FS_SETUP_SYMLINK, .target = "C" }, { .path = "E/F", .content = "F" }, - { .path = "E/L", .kind = FS_SETUP_SYMLINK, .target = "E/F" }, + { .path = "E/L", .kind = FS_SETUP_SYMLINK, .target = "F" }, }, .op = OP_COPY_TREE, .mode = SP_FS_ATOMIC_EXCLUSIVE, @@ -390,7 +390,7 @@ static const test_t tests [] = { .dst = "E", .err = SP_ERR_SYS_EXISTS, .expect = { - { .path = "E/L", .exists = true, .kind = SP_FS_KIND_SYMLINK, .target = "E/F" }, + { .path = "E/L", .exists = true, .kind = SP_FS_KIND_SYMLINK, .target = "F" }, }, }, { @@ -514,7 +514,7 @@ static const test_t tests [] = { }; sp_test_each(fs, copy, test_t, tests) { - skip_if_symlinks_needed(t, it->setup); + if (fs_setup_needs_symlinks(it->setup)) sp_test_skip_without_symlinks(); sp_str_t sandbox = sp_test_dir(t); fs_apply_setup(t, sandbox, it->setup); diff --git a/test/fs/create_dir.c b/test/fs/create_dir.c index 56531bd7..98446fcb 100644 --- a/test/fs/create_dir.c +++ b/test/fs/create_dir.c @@ -131,7 +131,7 @@ static const test_t tests [] = { }; sp_test_each(fs, create_dir, test_t, tests) { - skip_if_symlinks_needed(t, it->setup); + if (fs_setup_needs_symlinks(it->setup)) sp_test_skip_without_symlinks(); sp_str_t sandbox = sp_test_dir(t); fs_apply_setup(t, sandbox, it->setup); diff --git a/test/fs/dir/os.c b/test/fs/dir/os.c index e14cb71b..4f254a6e 100644 --- a/test/fs/dir/os.c +++ b/test/fs/dir/os.c @@ -88,7 +88,7 @@ static const test_t tests [] = { }; sp_test_each(fs, dir, test_t, tests) { - skip_if_symlinks_needed(t, it->setup); + if (fs_setup_needs_symlinks(it->setup)) sp_test_skip_without_symlinks(); sp_mem_t mem = sp_test_arena(t); sp_str_t sandbox = sp_test_dir(t); diff --git a/test/fs/fs.h b/test/fs/fs.h index 32cc7dfc..400a7fbc 100644 --- a/test/fs/fs.h +++ b/test/fs/fs.h @@ -24,36 +24,6 @@ static sp_str_t fs_path_c(sp_test_t* t, const c8* relative) { } -/////////////////// -// SYMLINK PROBE // -/////////////////// -static sp_test_once_t fs_symlink_probe = sp_zero; - -static sp_err_t fs_probe_symlinks(void* user) { - sp_str_t dir = *(sp_str_t*)user; - - sp_mem_arena_marker_t scratch = sp_mem_begin_scratch(); - sp_str_t target = sp_fs_join_path(scratch.mem, dir, sp_str_lit("probe_target")); - sp_str_t link = sp_fs_join_path(scratch.mem, dir, sp_str_lit("probe_link")); - - sp_fs_create_file(target); - sp_err_t err = sp_fs_create_sym_link(target, link); - if (!err) sp_fs_remove_file(link); - sp_fs_remove_file(target); - - sp_mem_end_scratch(scratch); - return err; -} - -static bool fs_symlinks_available(sp_test_t* t) { - sp_str_t dir = sp_test_dir(t); - return sp_test_once(&fs_symlink_probe, fs_probe_symlinks, &dir) == SP_OK; -} - -#define fs_skip_if_no_symlinks(t) \ - do { if (!fs_symlinks_available(t)) return sp_test_skip(t, "symlinks not available"); } while (0) - - //////////////////// // SHARED HARNESS // //////////////////// @@ -89,9 +59,6 @@ static bool fs_setup_needs_symlinks(const fs_setup_t setup [FS_MAX_SETUP]) { return false; } -#define skip_if_symlinks_needed(T, SETUP) \ - do { if (fs_setup_needs_symlinks(SETUP)) fs_skip_if_no_symlinks(T); } while (0) - static void fs_expect_bool(sp_test_t* t, sp_str_t path, const c8* label, bool actual, bool expected) { if (actual == expected) return; @@ -147,40 +114,33 @@ static void fs_apply_setup(sp_test_t* t, sp_str_t sandbox, const fs_setup_t setu const fs_setup_t* ent = &setup[it]; if (!ent->path) break; - sp_str_t path = sp_fs_join_path(mem, sandbox, sp_str_view(ent->path)); - sp_str_t parent = sp_fs_parent_path(path); - - if (!sp_str_empty(parent) && !sp_str_equal(parent, path) && !sp_fs_exists(parent)) { - sp_fs_create_dir(parent); - } + sp_str_t path = sp_fs_join_path(mem, sandbox, sp_cstr_as_str(ent->path)); + sp_fs_create_dir(sp_fs_parent_path(path)); + sp_err_t err = SP_OK; switch (ent->kind) { case FS_SETUP_FILE: { - sp_fs_create_file_str(path, ent->content ? sp_str_view(ent->content) : sp_str_lit("")); + err = sp_fs_create_file_str(path, ent->content ? sp_cstr_as_str(ent->content) : sp_str_lit("")); break; } case FS_SETUP_DIR: { - sp_fs_create_dir(path); + err = sp_fs_create_dir(path); break; } case FS_SETUP_SYMLINK: { - sp_str_t target = sp_fs_join_path(mem, sandbox, sp_str_view(ent->target)); - if (sp_fs_create_sym_link(target, path) != SP_OK) { - sp_test_fail(t, "failed to create symlink {} -> {}", sp_fmt_str(path), sp_fmt_str(target)); - } + err = sp_fs_create_sym_link(sp_cstr_as_str(ent->target), path); break; } case FS_SETUP_FIFO: { #if defined(SP_POSIX) - if (mkfifo(sp_cstr_from_str(mem, path), 0644) != 0) { - sp_test_fail(t, "failed to create fifo {}", sp_fmt_str(path)); - } + err = mkfifo(sp_cstr_from_str(mem, path), 0644) ? SP_ERR_SYS : SP_OK; #else - sp_test_fail(t, "fifo setup requires posix"); + err = SP_ERR_SYS_UNSUPPORTED; #endif break; } } + if (err) sp_test_fail(t, "failed to create {}: {}", sp_fmt_str(path), sp_fmt_str(sp_test_err_str(t, err))); } } @@ -200,7 +160,7 @@ static void fs_expect_paths(sp_test_t* t, sp_str_t sandbox, const fs_expected_pa const fs_expected_path_t* info = &expected[it]; if (!info->path) break; - sp_str_t path = sp_fs_join_path(mem, sandbox, sp_str_view(info->path)); + sp_str_t path = sp_fs_join_path(mem, sandbox, sp_cstr_as_str(info->path)); bool exists = sp_fs_exists(path); if (exists != info->exists) { @@ -214,7 +174,7 @@ static void fs_expect_paths(sp_test_t* t, sp_str_t sandbox, const fs_expected_pa if (info->content) { sp_str_t actual = sp_zero; sp_io_read_file(mem, path, &actual); - sp_str_t content = sp_str_view(info->content); + sp_str_t content = sp_cstr_as_str(info->content); if (!sp_str_equal(actual, content)) { sp_test_record(t, (sp_test_failure_t) { .message = sp_test_format(t, "content of {}", sp_fmt_str(path)), @@ -225,14 +185,10 @@ static void fs_expect_paths(sp_test_t* t, sp_str_t sandbox, const fs_expected_pa } if (info->target) { - sp_str_t expected = sp_fs_join_path(mem, sandbox, sp_cstr_as_str(info->target)); -#if defined(SP_WIN32) - expected = sp_str_replace_c8(mem, expected, '/', '\\'); -#endif c8 buf [SP_PATH_MAX]; sp_str_t actual = sp_zero; sp_expect_ok(t, sp_sys_readlink_s(sp_sys_get_root(0), path, buf, sizeof(buf), &actual)); - sp_expect_str_eq(t, actual, expected); + sp_expect_str_eq_c(t, sp_fs_normalize_path(mem, actual), info->target); } } } diff --git a/test/fs/it/enter.c b/test/fs/it/enter.c index 8d25c0ab..fa70cece 100644 --- a/test/fs/it/enter.c +++ b/test/fs/it/enter.c @@ -73,7 +73,7 @@ static const test_t tests [] = { .setup = { { "R/A", FS_SETUP_DIR }, { "R/A/B" }, - { .path = "R/L", .kind = FS_SETUP_SYMLINK, .target = "R/A" }, + { .path = "R/L", .kind = FS_SETUP_SYMLINK, .target = "A" }, }, .enter = { { "L", SP_ERR_SYS_LOOP } }, .expect = { @@ -109,7 +109,7 @@ static const enter_t* find_enter(sp_mem_t mem, const test_t* test, sp_str_t base } sp_test_each(fs, it_enter, test_t, tests) { - skip_if_symlinks_needed(t, it->setup); + if (fs_setup_needs_symlinks(it->setup)) sp_test_skip_without_symlinks(); sp_mem_t mem = sp_test_arena(t); sp_str_t sandbox = sp_test_dir(t); diff --git a/test/fs/it/os.c b/test/fs/it/os.c index 8ffff94e..37499256 100644 --- a/test/fs/it/os.c +++ b/test/fs/it/os.c @@ -105,7 +105,7 @@ static const test_t tests [] = { .setup = { { "R/A", FS_SETUP_DIR }, { "R/A/B" }, - { .path = "R/L", .kind = FS_SETUP_SYMLINK, .target = "R/A" }, + { .path = "R/L", .kind = FS_SETUP_SYMLINK, .target = "A" }, }, .recursive = true, .expect = { @@ -161,7 +161,7 @@ static const test_t tests [] = { }; sp_test_each(fs, it, test_t, tests) { - skip_if_symlinks_needed(t, it->setup); + if (fs_setup_needs_symlinks(it->setup)) sp_test_skip_without_symlinks(); sp_mem_t mem = sp_test_arena(t); sp_str_t sandbox = sp_test_dir(t); diff --git a/test/fs/links.c b/test/fs/links.c index 50adf378..58ba3cb0 100644 --- a/test/fs/links.c +++ b/test/fs/links.c @@ -1,23 +1,27 @@ #include "fs.h" typedef struct { + const c8* name; + fs_setup_t setup [FS_MAX_SETUP]; + const c8* target; + const c8* link; + const c8* rewrite; sp_err_t err; - fs_expected_path_t paths [FS_MAX_PATHS]; -} expect_t; + fs_expected_path_t expect [FS_MAX_PATHS]; +} hard_t; typedef struct { const c8* name; fs_setup_t setup [FS_MAX_SETUP]; - bool symlink; const c8* target; const c8* link; - const c8* rewrite; - expect_t expect; -} test_t; + sp_err_t err; + fs_expected_path_t expect [FS_MAX_PATHS]; +} sym_t; -static const test_t tests [] = { +static const hard_t hard_links [] = { { - .name = "hard_link_file", + .name = "file", .setup = { { .path = "A", .content = "A" }, }, @@ -25,126 +29,124 @@ static const test_t tests [] = { .link = "B", .rewrite = "B", .expect = { - .paths = { - { .path = "A", .exists = true, .kind = SP_FS_KIND_FILE }, - { .path = "B", .exists = true, .kind = SP_FS_KIND_FILE, .content = "B" }, - }, + { .path = "A", .exists = true, .kind = SP_FS_KIND_FILE }, + { .path = "B", .exists = true, .kind = SP_FS_KIND_FILE, .content = "B" }, }, }, { - .name = "hard_link_existing_destination_fails", + .name = "existing_destination_fails", .setup = { { .path = "A", .content = "A" }, { .path = "B", .content = "B" }, }, .target = "A", .link = "B", + .err = SP_ERR_SYS_EXISTS, .expect = { - .err = SP_ERR_SYS_EXISTS, - .paths = { - { .path = "A", .exists = true, .kind = SP_FS_KIND_FILE }, - { .path = "B", .exists = true, .kind = SP_FS_KIND_FILE }, - }, + { .path = "A", .exists = true, .kind = SP_FS_KIND_FILE }, + { .path = "B", .exists = true, .kind = SP_FS_KIND_FILE }, }, }, { - .name = "hard_link_directory_fails", + .name = "directory_fails", .setup = { { "A", FS_SETUP_DIR }, }, .target = "A", .link = "B", - .expect = { - // hard-linking a directory: POSIX reports EPERM; NT reports - // STATUS_FILE_IS_A_DIRECTORY from FileLinkInformation + // hard-linking a directory: POSIX reports EPERM; NT reports + // STATUS_FILE_IS_A_DIRECTORY from FileLinkInformation #if defined(SP_WIN32) - .err = SP_ERR_SYS_IS_DIR, + .err = SP_ERR_SYS_IS_DIR, #else - .err = SP_ERR_SYS_ACCESS_DENIED, + .err = SP_ERR_SYS_ACCESS_DENIED, #endif - .paths = { - { .path = "A", .exists = true, .kind = SP_FS_KIND_DIR }, - { .path = "B" }, - }, + .expect = { + { .path = "A", .exists = true, .kind = SP_FS_KIND_DIR }, + { .path = "B" }, }, }, +}; + +static const sym_t sym_links [] = { { - .name = "symlink_file", + .name = "file", .setup = { { .path = "A", .content = "A" }, }, - .symlink = true, .target = "A", .link = "L", .expect = { - .paths = { - { .path = "A", .exists = true, .kind = SP_FS_KIND_FILE }, - { .path = "L", .exists = true, .kind = SP_FS_KIND_SYMLINK, .content = "A" }, - }, + { .path = "A", .exists = true, .kind = SP_FS_KIND_FILE }, + { .path = "L", .exists = true, .kind = SP_FS_KIND_SYMLINK, .content = "A", .target = "A" }, }, }, { - .name = "symlink_directory", + .name = "directory", .setup = { { "A", FS_SETUP_DIR }, }, - .symlink = true, .target = "A", .link = "L", .expect = { - .paths = { - { .path = "A", .exists = true, .kind = SP_FS_KIND_DIR }, - { .path = "L", .exists = true, .kind = SP_FS_KIND_SYMLINK }, - }, + { .path = "A", .exists = true, .kind = SP_FS_KIND_DIR }, + { .path = "L", .exists = true, .kind = SP_FS_KIND_SYMLINK, .target = "A" }, }, }, { - .name = "symlink_existing_destination_fails", + .name = "existing_destination_fails", .setup = { { .path = "A", .content = "A" }, { .path = "B", .content = "B" }, }, - .symlink = true, .target = "A", .link = "B", + .err = SP_ERR_SYS_EXISTS, .expect = { - .err = SP_ERR_SYS_EXISTS, - .paths = { - { .path = "A", .exists = true, .kind = SP_FS_KIND_FILE }, - { .path = "B", .exists = true, .kind = SP_FS_KIND_FILE }, - }, + { .path = "A", .exists = true, .kind = SP_FS_KIND_FILE }, + { .path = "B", .exists = true, .kind = SP_FS_KIND_FILE }, }, }, }; -sp_test_each(fs, links, test_t, tests) { - if (it->symlink) fs_skip_if_no_symlinks(t); - - sp_mem_t mem = sp_test_arena(t); +sp_test_each(fs, hard_link, hard_t, hard_links) { sp_str_t sandbox = sp_test_dir(t); fs_apply_setup(t, sandbox, it->setup); sp_sys_fd_t dir = SP_SYS_INVALID_FD; sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, 0, &dir)); - sp_path_t target = { .dir = dir, .sub = sp_str_view(it->target) }; - sp_path_t link = { .dir = dir, .sub = sp_str_view(it->link) }; - - sp_err_t result = it->symlink - ? sp_fs_create_sym_link_at(sp_fs_join_path(mem, sandbox, target.sub), link) - : sp_fs_create_hard_link_at(target, link); - sp_expect_err_eq(t, result, it->expect.err); + sp_path_t target = { .dir = dir, .sub = sp_cstr_as_str(it->target) }; + sp_path_t link = { .dir = dir, .sub = sp_cstr_as_str(it->link) }; + sp_expect_err_eq(t, sp_fs_create_hard_link_at(target, link), it->err); // a hard link shares content with its target: rewrite the target through // one name, then the expected paths observe the update through the other if (it->rewrite) { sp_io_file_writer_t writer = sp_zero; sp_io_file_writer_from_path_at(&writer, target); - sp_io_write_str(&writer.base, sp_str_view(it->rewrite), SP_NULLPTR); + sp_io_write_str(&writer.base, sp_cstr_as_str(it->rewrite), SP_NULLPTR); sp_io_file_writer_close(&writer); } sp_sys_close(dir); - fs_expect_paths(t, sandbox, it->expect.paths); + fs_expect_paths(t, sandbox, it->expect); + return SP_OK; +} + +sp_test_each(fs, sym_link, sym_t, sym_links) { + sp_test_skip_without_symlinks(); + + sp_str_t sandbox = sp_test_dir(t); + fs_apply_setup(t, sandbox, it->setup); + + sp_sys_fd_t dir = SP_SYS_INVALID_FD; + sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, 0, &dir)); + + sp_path_t link = { .dir = dir, .sub = sp_cstr_as_str(it->link) }; + sp_expect_err_eq(t, sp_fs_create_sym_link_at(sp_cstr_as_str(it->target), link), it->err); + sp_sys_close(dir); + + fs_expect_paths(t, sandbox, it->expect); return SP_OK; } diff --git a/test/fs/predicates.c b/test/fs/predicates.c index c4f12710..b8a9359c 100644 --- a/test/fs/predicates.c +++ b/test/fs/predicates.c @@ -89,7 +89,7 @@ static const test_t tests [] = { }; sp_test_each(fs, predicates, test_t, tests) { - skip_if_symlinks_needed(t, it->setup); + if (fs_setup_needs_symlinks(it->setup)) sp_test_skip_without_symlinks(); sp_str_t sandbox = sp_test_dir(t); fs_apply_setup(t, sandbox, it->setup); diff --git a/test/fs/remove/os.c b/test/fs/remove/os.c index 16fe2b10..b2ec7d4f 100644 --- a/test/fs/remove/os.c +++ b/test/fs/remove/os.c @@ -189,7 +189,7 @@ static const test_t tests [] = { { "B", FS_SETUP_DIR }, { "B/C" }, { "A", FS_SETUP_DIR }, - { .path = "A/L", .kind = FS_SETUP_SYMLINK, .target = "B" }, + { .path = "A/L", .kind = FS_SETUP_SYMLINK, .target = "../B" }, { "A/D" }, }, .dir = true, @@ -260,7 +260,7 @@ static const test_t tests [] = { }; sp_test_each(fs, remove, test_t, tests) { - skip_if_symlinks_needed(t, it->setup); + if (fs_setup_needs_symlinks(it->setup)) sp_test_skip_without_symlinks(); sp_mem_t mem = sp_test_arena(t); sp_str_t sandbox = sp_test_dir(t); diff --git a/test/sys/harness.h b/test/sys/harness.h index fa1bfdbf..e14e9f42 100644 --- a/test/sys/harness.h +++ b/test/sys/harness.h @@ -59,8 +59,6 @@ typedef struct { sys_expect_t expect [SYS_CASE_MAX_EXPECT]; } sys_case_t; -static sp_test_once_t sys_symlink_probe = sp_zero; - sp_err_t sys_case_run(sp_test_t* t, sys_case_t* c); #endif diff --git a/test/sys/path.c b/test/sys/path.c index db7764a5..0d938bd6 100644 --- a/test/sys/path.c +++ b/test/sys/path.c @@ -372,22 +372,17 @@ static sp_err_t run(sp_test_t* t, test_t* c) { return sp_test_skip(t, "posix paths only"); } + if (c->link) sp_test_skip_without_symlinks(); + sp_mem_t mem = sp_test_arena(t); sp_str_t sandbox = sp_test_dir(t); + if (c->file) sp_must_ok(t, sp_fs_create_file_str(sp_fs_join_path(mem, sandbox, sp_cstr_as_str(c->file)), sp_str_lit("A"))); + if (c->dir) sp_must_ok(t, sp_fs_create_dir(sp_fs_join_path(mem, sandbox, sp_cstr_as_str(c->dir)))); + if (c->link) sp_must_ok(t, sp_fs_create_sym_link(sp_cstr_as_str(c->target), sp_fs_join_path(mem, sandbox, sp_cstr_as_str(c->link)))); + sp_sys_fd_t sandbox_fd = SP_SYS_INVALID_FD; sp_try(sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, 0, &sandbox_fd)); - if (c->file) sp_fs_create_file_str(sp_fs_join_path(mem, sandbox, sp_cstr_as_str(c->file)), sp_str_lit("A")); - if (c->dir) sp_fs_create_dir(sp_fs_join_path(mem, sandbox, sp_cstr_as_str(c->dir))); - if (c->link) { - sp_str_t target = sp_fs_join_path(mem, sandbox, sp_cstr_as_str(c->target)); - sp_str_t link = sp_fs_join_path(mem, sandbox, sp_cstr_as_str(c->link)); - if (sp_fs_create_sym_link(target, link)) { - sp_sys_close(sandbox_fd); - return sp_test_skip(t, "symlinks not available"); - } - } - sp_str_t path = sp_cstr_as_str(c->path); if (c->path_kind == PATH_LONG) path = long_path(mem, c->path); if (c->path_kind == PATH_ABS) path = abs_path(mem, sandbox, c->path); diff --git a/test/sys/readlink.c b/test/sys/readlink.c index 5107c696..ab6bc79d 100644 --- a/test/sys/readlink.c +++ b/test/sys/readlink.c @@ -46,16 +46,14 @@ static sp_err_t run(sp_test_t* t, test_t* c) { break; } case SETUP_LINK: { - if (sp_fs_create_sym_link(sp_cstr_as_str(c->target), path)) { - return sp_test_skip(t, "symlinks not available"); - } + sp_test_skip_without_symlinks(); + sp_must_ok(t, sp_fs_create_sym_link(sp_cstr_as_str(c->target), path)); break; } case SETUP_LINK_ABSOLUTE: { + sp_test_skip_without_symlinks(); expected = sp_fs_join_path(mem, sandbox, sp_cstr_as_str(c->target)); - if (sp_fs_create_sym_link(expected, path)) { - return sp_test_skip(t, "symlinks not available"); - } + sp_must_ok(t, sp_fs_create_sym_link(expected, path)); break; } } diff --git a/test/sys/sys.c b/test/sys/sys.c index 16990a91..61ea0f4a 100644 --- a/test/sys/sys.c +++ b/test/sys/sys.c @@ -1,21 +1,5 @@ #include "harness.h" -static sp_err_t sys_probe_symlinks(void* user) { - sp_str_t dir = *(sp_str_t*)user; - - sp_mem_arena_marker_t scratch = sp_mem_begin_scratch(); - sp_str_t target = sp_fs_join_path(scratch.mem, dir, sp_str_lit("probe_target")); - sp_str_t link = sp_fs_join_path(scratch.mem, dir, sp_str_lit("probe_link")); - - sp_fs_create_file(target); - sp_err_t err = sp_fs_create_sym_link(target, link); - if (!err) sp_fs_remove_file(link); - sp_fs_remove_file(target); - - sp_mem_end_scratch(scratch); - return err; -} - static bool sys_case_wants_symlinks(sys_case_t* c) { sp_carr_for(c->setup, it) { if (!c->setup[it].path) break; @@ -49,13 +33,10 @@ sp_err_t sys_case_run(sp_test_t* t, sys_case_t* c) { sp_carr_for(fds, it) fds[it] = SP_SYS_INVALID_FD; sp_sys_fd_t sandbox_fd = SP_SYS_INVALID_FD; + if (sys_case_wants_symlinks(c)) sp_test_skip_without_symlinks(); + sp_mem_t mem = sp_test_arena(t); sp_str_t sandbox = sp_test_dir(t); - - if (sys_case_wants_symlinks(c) && sp_test_once(&sys_symlink_probe, sys_probe_symlinks, &sandbox)) { - return sp_test_skip(t, "symlinks not available"); - } - sp_try(sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, 0, &sandbox_fd)); sp_carr_for(c->setup, it) { @@ -73,7 +54,7 @@ sp_err_t sys_case_run(sp_test_t* t, sys_case_t* c) { break; } case SYS_SETUP_SYMLINK: { - sp_str_t target = sp_fs_join_path(mem, sandbox, sp_cstr_as_str(ent->target)); + sp_str_t target = sp_cstr_as_str(ent->target); if (sp_fs_create_sym_link(target, path)) { sp_test_fail(t, "failed to create symlink {} -> {}", sp_fmt_str(path), sp_fmt_str(target)); goto done; diff --git a/test/sys/sys.h b/test/sys/sys.h index 1a05efae..8d6d65b0 100644 --- a/test/sys/sys.h +++ b/test/sys/sys.h @@ -174,7 +174,7 @@ static bool sys_apply_setup(s32* utest_result, sp_test_file_manager_t* fm, sp_st break; } case SYS_SETUP_SYMLINK: { - sp_str_t target = sp_fs_join_path(fm->mem, sandbox, sp_cstr_as_str(ent->target)); + sp_str_t target = sp_cstr_as_str(ent->target); if (sp_fs_create_sym_link(target, path) != SP_OK) { SP_TEST_REPORT("failed to create symlink {} -> {}", sp_fmt_str(path), sp_fmt_str(target)); SP_FAIL(); From cafe295d5c2d9888ea415832dc69c45755cf1209 Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Tue, 29 Sep 2026 00:31:29 -0400 Subject: [PATCH 48/72] run wasm with a preopen --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index afac35bf..2a0d0ead 100644 --- a/Makefile +++ b/Makefile @@ -50,7 +50,7 @@ else endif ifneq (,$(findstring wasm32,$(TRIPLE))) - RUNNER = wasmtime run + RUNNER = wasmtime run --dir $(CURDIR) endif LDLIBS_PLATFORM = From 04a005cdaf80d04632164c105d75145c01ebd5e3 Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Tue, 29 Sep 2026 00:45:35 -0400 Subject: [PATCH 49/72] msvc --- tools/windows/sp/spn/amalgamation.vcxproj | 2 +- tools/windows/sp/spn/fs.vcxproj | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tools/windows/sp/spn/amalgamation.vcxproj b/tools/windows/sp/spn/amalgamation.vcxproj index 20aec49c..628702f8 100644 --- a/tools/windows/sp/spn/amalgamation.vcxproj +++ b/tools/windows/sp/spn/amalgamation.vcxproj @@ -18,7 +18,7 @@ amalgamation 10.0 amalgamation - ..\..\..\..\test\main.c;..\..\..\..\test\test\axis.c;..\..\..\..\test\test\filter.c;..\..\..\..\test\test\matrix.c;..\..\..\..\test\test\resolve.c;..\..\..\..\test\test\sweep.c;..\..\..\..\test\test\tracking.c;..\..\..\..\test\sys\sys.c;..\..\..\..\test\sys\open.c;..\..\..\..\test\sys\path.c;..\..\..\..\test\sys\futex.c;..\..\..\..\test\sys\fd.c;..\..\..\..\test\sys\fd_path.c;..\..\..\..\test\sys\nanosleep.c;..\..\..\..\test\sys\timestamps.c;..\..\..\..\test\sys\perms.c;..\..\..\..\test\sys\self_path.c;..\..\..\..\test\sys\pipe\data.c;..\..\..\..\test\sys\pipe\blocking.c;..\..\..\..\test\sys\transfer.c;..\..\..\..\test\sys\readlink.c;..\..\..\..\test\sys\socket\data.c;..\..\..\..\test\sys\socket\listen.c;..\..\..\..\test\sys\socket\desc.c;..\..\..\..\test\sys\tty\contract.c;..\..\..\..\test\sys\tty\mode.c;..\..\..\..\test\sys\wait\event.c;..\..\..\..\test\sys\wait\wait.c;..\..\..\..\test\sys\inherit.c;..\..\..\..\test\fs\dir\*.c;..\..\..\..\test\fs\it\*.c;..\..\..\..\test\fs\wtf8\*.c;..\..\..\..\test\fs\*.c;..\..\..\..\test\fs\windows\nt_path.c;..\..\..\..\test\msvc\*.c;..\..\..\..\test\mem\uninitialized.c;..\..\..\..\test\atomic.c;..\..\..\..\test\sync\*.c;..\..\..\..\test\sync\os\*.c + ..\..\..\..\test\main.c;..\..\..\..\test\test\axis.c;..\..\..\..\test\test\filter.c;..\..\..\..\test\test\matrix.c;..\..\..\..\test\test\resolve.c;..\..\..\..\test\test\sweep.c;..\..\..\..\test\test\tracking.c;..\..\..\..\test\sys\sys.c;..\..\..\..\test\sys\open.c;..\..\..\..\test\sys\path.c;..\..\..\..\test\sys\futex.c;..\..\..\..\test\sys\fd.c;..\..\..\..\test\sys\fd_path.c;..\..\..\..\test\sys\nanosleep.c;..\..\..\..\test\sys\timestamps.c;..\..\..\..\test\sys\perms.c;..\..\..\..\test\sys\self_path.c;..\..\..\..\test\sys\pipe\data.c;..\..\..\..\test\sys\pipe\blocking.c;..\..\..\..\test\sys\transfer.c;..\..\..\..\test\sys\readlink.c;..\..\..\..\test\sys\socket\data.c;..\..\..\..\test\sys\socket\listen.c;..\..\..\..\test\sys\socket\desc.c;..\..\..\..\test\sys\tty\contract.c;..\..\..\..\test\sys\tty\mode.c;..\..\..\..\test\sys\wait\event.c;..\..\..\..\test\sys\wait\wait.c;..\..\..\..\test\sys\inherit.c;..\..\..\..\test\fs\dir\*.c;..\..\..\..\test\fs\it\*.c;..\..\..\..\test\fs\remove\*.c;..\..\..\..\test\fs\wtf8\*.c;..\..\..\..\test\fs\*.c;..\..\..\..\test\fs\windows\nt_path.c;..\..\..\..\test\msvc\*.c;..\..\..\..\test\mem\uninitialized.c;..\..\..\..\test\atomic.c;..\..\..\..\test\sync\*.c;..\..\..\..\test\sync\os\*.c ..\..\..\..\sp.h;..\..\..\..\sp\sp_test.h false diff --git a/tools/windows/sp/spn/fs.vcxproj b/tools/windows/sp/spn/fs.vcxproj index 1d92a2fa..dd27df88 100644 --- a/tools/windows/sp/spn/fs.vcxproj +++ b/tools/windows/sp/spn/fs.vcxproj @@ -18,7 +18,7 @@ fs 10.0 fs - ..\..\..\..\test\main.c;..\..\..\..\test\fs\dir\*.c;..\..\..\..\test\fs\it\*.c;..\..\..\..\test\fs\wtf8\*.c;..\..\..\..\test\fs\*.c;..\..\..\..\test\fs\windows\nt_path.c + ..\..\..\..\test\main.c;..\..\..\..\test\fs\dir\*.c;..\..\..\..\test\fs\it\*.c;..\..\..\..\test\fs\remove\*.c;..\..\..\..\test\fs\wtf8\*.c;..\..\..\..\test\fs\*.c;..\..\..\..\test\fs\windows\nt_path.c ..\..\..\..\sp.h;..\..\..\..\sp\sp_test.h false From 8b72c562f185637943a3e1dad1de88f42c9ba636 Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Tue, 29 Sep 2026 02:02:35 -0400 Subject: [PATCH 50/72] symlinks must specify a kind --- sp.h | 63 +++++++++++++++++------------------------- sp/sp_test.h | 2 +- test/fs/canonicalize.c | 2 +- test/fs/copy.c | 19 +++++++++++-- test/fs/create_dir.c | 4 +-- test/fs/fs.h | 9 ++++-- test/fs/it/enter.c | 2 +- test/fs/it/os.c | 2 +- test/fs/links.c | 19 +++++++++++-- test/fs/predicates.c | 2 +- test/fs/remove/os.c | 6 ++-- test/sys/harness.h | 1 + test/sys/open.c | 4 +-- test/sys/path.c | 9 +++++- test/sys/readlink.c | 4 +-- test/sys/symlink.c | 28 +++++++++++++++++-- test/sys/sys.c | 8 ++++-- test/sys/sys.h | 17 +++++++----- 18 files changed, 130 insertions(+), 71 deletions(-) diff --git a/sp.h b/sp.h index dd31f29e..b22bbbe5 100644 --- a/sp.h +++ b/sp.h @@ -1471,7 +1471,7 @@ SP_API sp_err_t sp_sys_rmdir(sp_sys_fd_t fd, const c8* path, u32 len); SP_API sp_err_t sp_sys_unlink(sp_sys_fd_t fd, const c8* path, u32 len); SP_API sp_err_t sp_sys_rename(sp_sys_fd_t from, const c8* pfrom, u32 lf, sp_sys_fd_t to, const c8* pto, u32 lt); SP_API sp_err_t sp_sys_link(sp_sys_fd_t from, const c8* pfrom, u32 lf, sp_sys_fd_t to, const c8* pto, u32 lt); -SP_API sp_err_t sp_sys_symlink(const c8* from, u32 lf, sp_sys_fd_t to, const c8* pto, u32 lt); +SP_API sp_err_t sp_sys_symlink(const c8* from, u32 lf, sp_sys_fd_t to, const c8* pto, u32 lt, sp_fs_kind_t kind); SP_API sp_err_t sp_sys_readlink(sp_sys_fd_t fd, const c8* path, u32 len, c8* buf, u64 size, u64* target_len); SP_API sp_err_t sp_sys_get_path_metadata(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_file_meta_t* st); SP_API sp_err_t sp_sys_get_link_metadata(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_file_meta_t* st); @@ -1531,7 +1531,7 @@ SP_API sp_err_t sp_sys_unlink_s(sp_sys_fd_t fd, sp_str_t path); SP_API sp_err_t sp_sys_rename_s(sp_sys_fd_t from_fd, sp_str_t from, sp_sys_fd_t to_fd, sp_str_t to); SP_API sp_err_t sp_sys_chdir_s(sp_str_t path); SP_API sp_err_t sp_sys_link_s(sp_sys_fd_t from_fd, sp_str_t existing, sp_sys_fd_t to_fd, sp_str_t alias); -SP_API sp_err_t sp_sys_symlink_s(sp_str_t existing, sp_sys_fd_t to_fd, sp_str_t alias); +SP_API sp_err_t sp_sys_symlink_s(sp_str_t existing, sp_sys_fd_t to_fd, sp_str_t alias, sp_fs_kind_t kind); SP_API sp_err_t sp_sys_readlink_s(sp_sys_fd_t fd, sp_str_t path, c8* buf, u64 size, sp_str_t* target); SP_API sp_err_t sp_sys_set_file_perms_s(sp_sys_fd_t fd, sp_str_t path, sp_sys_file_perms_t perms); SP_API sp_err_t sp_sys_set_times_s(sp_sys_fd_t fd, sp_str_t path, sp_sys_timespec_t atime, sp_sys_timespec_t mtime); @@ -1570,7 +1570,7 @@ typedef struct { sp_err_t (*unlink)(sp_sys_fd_t fd, const c8* path, u32 len); sp_err_t (*rename)(sp_sys_fd_t from_fd, const c8* from, u32 from_len, sp_sys_fd_t to_fd, const c8* to, u32 to_len); sp_err_t (*link)(sp_sys_fd_t from_fd, const c8* existing, u32 existing_len, sp_sys_fd_t to_fd, const c8* alias, u32 alias_len); - sp_err_t (*symlink)(const c8* existing, u32 existing_len, sp_sys_fd_t to_fd, const c8* alias, u32 alias_len); + sp_err_t (*symlink)(const c8* existing, u32 existing_len, sp_sys_fd_t to_fd, const c8* alias, u32 alias_len, sp_fs_kind_t kind); sp_err_t (*readlink)(sp_sys_fd_t fd, const c8* path, u32 len, c8* buf, u64 size, u64* target_len); sp_err_t (*get_path_metadata)(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_file_meta_t* st); sp_err_t (*get_link_metadata)(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_file_meta_t* st); @@ -1645,7 +1645,7 @@ SP_API sp_err_t sp_sys_rmdir_p(sp_sys_fd_t fd, const c8* path, u32 len); SP_API sp_err_t sp_sys_unlink_p(sp_sys_fd_t fd, const c8* path, u32 len); SP_API sp_err_t sp_sys_rename_p(sp_sys_fd_t from_fd, const c8* from, u32 from_len, sp_sys_fd_t to_fd, const c8* to, u32 to_len); SP_API sp_err_t sp_sys_link_p(sp_sys_fd_t from_fd, const c8* existing, u32 existing_len, sp_sys_fd_t to_fd, const c8* alias, u32 alias_len); -SP_API sp_err_t sp_sys_symlink_p(const c8* existing, u32 existing_len, sp_sys_fd_t to_fd, const c8* alias, u32 alias_len); +SP_API sp_err_t sp_sys_symlink_p(const c8* existing, u32 existing_len, sp_sys_fd_t to_fd, const c8* alias, u32 alias_len, sp_fs_kind_t kind); SP_API sp_err_t sp_sys_readlink_p(sp_sys_fd_t fd, const c8* path, u32 len, c8* buf, u64 size, u64* target_len); SP_API sp_err_t sp_sys_get_path_metadata_p(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_file_meta_t* st); SP_API sp_err_t sp_sys_get_link_metadata_p(sp_sys_fd_t fd, const c8* path, u32 len, sp_sys_file_meta_t* st); @@ -3115,8 +3115,8 @@ SP_API sp_err_t sp_fs_remove_file(sp_str_t path); SP_API sp_err_t sp_fs_remove_file_at(sp_path_t path); SP_API sp_err_t sp_fs_create_hard_link(sp_str_t target, sp_str_t link_path); SP_API sp_err_t sp_fs_create_hard_link_at(sp_path_t target, sp_path_t link_path); -SP_API sp_err_t sp_fs_create_sym_link(sp_str_t target, sp_str_t link_path); -SP_API sp_err_t sp_fs_create_sym_link_at(sp_str_t target, sp_path_t link_path); +SP_API sp_err_t sp_fs_create_sym_link(sp_str_t target, sp_str_t link_path, sp_fs_kind_t kind); +SP_API sp_err_t sp_fs_create_sym_link_at(sp_str_t target, sp_path_t link_path, sp_fs_kind_t kind); SP_API sp_err_t sp_fs_copy_at(sp_path_t from, sp_path_t to, sp_fs_atomic_mode_t mode); SP_API sp_err_t sp_fs_copy_file_at(sp_path_t from, sp_path_t to, sp_fs_atomic_mode_t mode); SP_API sp_err_t sp_fs_copy_tree_at(sp_path_t from, sp_path_t to, sp_fs_atomic_mode_t mode); @@ -5964,8 +5964,8 @@ sp_err_t sp_sys_link(sp_sys_fd_t from, const c8* existing, u32 existing_len, sp_ return (sp_rt.vt->link)(from, existing, existing_len, to, alias, alias_len); } -sp_err_t sp_sys_symlink(const c8* existing, u32 existing_len, sp_sys_fd_t to_fd, const c8* alias, u32 alias_len) { - return (sp_rt.vt->symlink)(existing, existing_len, to_fd, alias, alias_len); +sp_err_t sp_sys_symlink(const c8* existing, u32 existing_len, sp_sys_fd_t to_fd, const c8* alias, u32 alias_len, sp_fs_kind_t kind) { + return (sp_rt.vt->symlink)(existing, existing_len, to_fd, alias, alias_len, kind); } sp_err_t sp_sys_readlink(sp_sys_fd_t fd, const c8* path, u32 len, c8* buf, u64 size, u64* target_len) { @@ -10795,7 +10795,14 @@ sp_err_t sp_sys_link_s(sp_sys_fd_t from_fd, sp_str_t existing, sp_sys_fd_t to_fd //////////////////// // SP_SYS_SYMLINK // //////////////////// -sp_err_t sp_sys_symlink_p(const c8* existing, u32 existing_len, sp_sys_fd_t to_fd, const c8* alias, u32 alias_len) { +sp_err_t sp_sys_symlink_p(const c8* existing, u32 existing_len, sp_sys_fd_t to_fd, const c8* alias, u32 alias_len, sp_fs_kind_t kind) { + switch (kind) { + case SP_FS_KIND_FILE: + case SP_FS_KIND_DIR: break; + case SP_FS_KIND_NONE: + case SP_FS_KIND_SYMLINK: return SP_ERR_SYS_INVALID; + } + #if defined(SP_WIN32) sp_str_t target = sp_str(existing, existing_len); sp_str_t link = sp_str(alias, alias_len); @@ -10816,26 +10823,7 @@ sp_err_t sp_sys_symlink_p(const c8* existing, u32 existing_len, sp_sys_fd_t to_f (wtarget.len >= 3 && wt[1] == ':' && wt[2] == '\\') || (wtarget.len >= 2 && wt[0] == '\\' && wt[1] == '\\'); - u32 dir_option = SP_NT_FILE_NON_DIRECTORY_FILE; - { - c8 probe_buf [SP_PATH_MAX]; - sp_str_t probe = target; - if (!absolute) { - u32 parent_len = 0; - sp_for(it, link.len) { - if (link.data[it] == '/' || link.data[it] == '\\') parent_len = (u32)it + 1; - } - if ((u64)parent_len + target.len <= sizeof(probe_buf)) { - sp_mem_copy(probe_buf, link.data, parent_len); - sp_mem_copy(probe_buf + parent_len, target.data, target.len); - probe = sp_str(probe_buf, parent_len + target.len); - } - } - sp_sys_file_meta_t meta = sp_zero; - if (!sp_sys_file_meta_from_nt_path(to_fd, probe, &meta, true) && meta.kind == SP_FS_KIND_DIR) { - dir_option = SP_NT_FILE_DIRECTORY_FILE; - } - } + u32 dir_option = kind == SP_FS_KIND_DIR ? SP_NT_FILE_DIRECTORY_FILE : SP_NT_FILE_NON_DIRECTORY_FILE; sp_wide_str_t stored = wtarget; sp_sys_nt_path_t nt_path = sp_zero; @@ -10924,8 +10912,8 @@ sp_err_t sp_sys_symlink_p(const c8* existing, u32 existing_len, sp_sys_fd_t to_f #endif } -sp_err_t sp_sys_symlink_s(sp_str_t existing, sp_sys_fd_t to_fd, sp_str_t alias) { - return sp_sys_symlink(existing.data, existing.len, to_fd, alias.data, alias.len); +sp_err_t sp_sys_symlink_s(sp_str_t existing, sp_sys_fd_t to_fd, sp_str_t alias, sp_fs_kind_t kind) { + return sp_sys_symlink(existing.data, existing.len, to_fd, alias.data, alias.len, kind); } ///////////////////// @@ -20563,12 +20551,12 @@ sp_err_t sp_fs_create_hard_link(sp_str_t target, sp_str_t link_path) { return sp_fs_create_hard_link_at(sp_path_at_cwd(target), sp_path_at_cwd(link_path)); } -sp_err_t sp_fs_create_sym_link_at(sp_str_t target, sp_path_t link_path) { - return sp_sys_symlink_s(target, link_path.dir, link_path.sub); +sp_err_t sp_fs_create_sym_link_at(sp_str_t target, sp_path_t link_path, sp_fs_kind_t kind) { + return sp_sys_symlink_s(target, link_path.dir, link_path.sub, kind); } -sp_err_t sp_fs_create_sym_link(sp_str_t target, sp_str_t link_path) { - return sp_fs_create_sym_link_at(target, sp_path_at_cwd(link_path)); +sp_err_t sp_fs_create_sym_link(sp_str_t target, sp_str_t link_path, sp_fs_kind_t kind) { + return sp_fs_create_sym_link_at(target, sp_path_at_cwd(link_path), kind); } sp_err_t sp_fs_remove_file_at(sp_path_t path) { @@ -21038,15 +21026,16 @@ SP_PRIVATE sp_err_t sp_fs_copy_link_at(sp_path_t from, sp_path_t to, sp_fs_atomi c8 buf [SP_PATH_MAX]; sp_str_t target = sp_zero; sp_try(sp_sys_readlink_s(from.dir, from.sub, buf, sizeof(buf), &target)); + sp_fs_kind_t kind = sp_fs_get_target_kind_at(from) == SP_FS_KIND_DIR ? SP_FS_KIND_DIR : SP_FS_KIND_FILE; switch (mode) { case SP_FS_ATOMIC_REPLACE: { sp_err_t err = sp_sys_unlink_s(to.dir, to.sub); if (err && err != SP_ERR_SYS_NOT_FOUND) return err; - return sp_sys_symlink_s(target, to.dir, to.sub); + return sp_sys_symlink_s(target, to.dir, to.sub, kind); } case SP_FS_ATOMIC_EXCLUSIVE: { - return sp_sys_symlink_s(target, to.dir, to.sub); + return sp_sys_symlink_s(target, to.dir, to.sub, kind); } } SP_UNREACHABLE_RETURN(SP_OK); diff --git a/sp/sp_test.h b/sp/sp_test.h index bea43772..c6cafaaf 100644 --- a/sp/sp_test.h +++ b/sp/sp_test.h @@ -1046,7 +1046,7 @@ static sp_err_t sp_test_probe_symlinks(void* user) { sp_mem_arena_marker_t s = sp_mem_begin_scratch(); sp_str_t link = sp_fs_join_path(s.mem, runner->dir_root, sp_str_lit("L")); sp_err_t err = sp_fs_create_dir(runner->dir_root); - if (!err) err = sp_fs_create_sym_link(sp_str_lit("A"), link); + if (!err) err = sp_fs_create_sym_link(sp_str_lit("A"), link, SP_FS_KIND_FILE); if (!err) sp_fs_remove_file(link); sp_mem_end_scratch(s); return err; diff --git a/test/fs/canonicalize.c b/test/fs/canonicalize.c index 35b4f7a1..44c3e4f2 100644 --- a/test/fs/canonicalize.c +++ b/test/fs/canonicalize.c @@ -120,7 +120,7 @@ static const test_t tests [] = { .name = "resolves_symlink_to_dir", .setup = { { "A", FS_SETUP_DIR }, - { .path = "L", .kind = FS_SETUP_SYMLINK, .target = "A" }, + { .path = "L", .kind = FS_SETUP_DIR_SYMLINK, .target = "A" }, }, .input = "L", .expect = { diff --git a/test/fs/copy.c b/test/fs/copy.c index c1814443..ce878311 100644 --- a/test/fs/copy.c +++ b/test/fs/copy.c @@ -135,7 +135,7 @@ static const test_t tests [] = { .setup = { { .path = "A", .content = "A" }, { "D", FS_SETUP_DIR }, - { .path = "L", .kind = FS_SETUP_SYMLINK, .target = "D" }, + { .path = "L", .kind = FS_SETUP_DIR_SYMLINK, .target = "D" }, }, .src = "A", .dst = "L/B", @@ -359,6 +359,21 @@ static const test_t tests [] = { { .path = "E/L", .exists = true, .kind = SP_FS_KIND_SYMLINK, .target = "C" }, }, }, + { + .name = "tree_symlink_to_dir_preserved", + .setup = { + { .path = "A/Z/F", .content = "F" }, + { .path = "A/L", .kind = FS_SETUP_DIR_SYMLINK, .target = "Z" }, + }, + .op = OP_COPY_TREE, + .src = "A", + .dst = "E", + .expect = { + { .path = "E/Z/F", .exists = true, .kind = SP_FS_KIND_FILE, .content = "F" }, + { .path = "E/L", .exists = true, .kind = SP_FS_KIND_SYMLINK, .target = "Z" }, + { .path = "E/L/F", .exists = true, .kind = SP_FS_KIND_FILE, .content = "F" }, + }, + }, { .name = "tree_symlink_replaced", .setup = { @@ -366,7 +381,7 @@ static const test_t tests [] = { { .path = "A/C", .content = "C" }, { .path = "A/L", .kind = FS_SETUP_SYMLINK, .target = "C" }, { "E", FS_SETUP_DIR }, - { .path = "E/L", .kind = FS_SETUP_SYMLINK, .target = "../A" }, + { .path = "E/L", .kind = FS_SETUP_DIR_SYMLINK, .target = "../A" }, }, .op = OP_COPY_TREE, .src = "A", diff --git a/test/fs/create_dir.c b/test/fs/create_dir.c index 98446fcb..1bcd8df3 100644 --- a/test/fs/create_dir.c +++ b/test/fs/create_dir.c @@ -76,7 +76,7 @@ static const test_t tests [] = { .name = "destination_is_symlink_to_directory", .setup = { { "A", FS_SETUP_DIR }, - { .path = "L", .kind = FS_SETUP_SYMLINK, .target = "A" }, + { .path = "L", .kind = FS_SETUP_DIR_SYMLINK, .target = "A" }, }, .target = "L", .expect = { @@ -90,7 +90,7 @@ static const test_t tests [] = { .name = "destination_under_symlink_to_directory", .setup = { { "A", FS_SETUP_DIR }, - { .path = "L", .kind = FS_SETUP_SYMLINK, .target = "A" }, + { .path = "L", .kind = FS_SETUP_DIR_SYMLINK, .target = "A" }, }, .target = "L/B", .expect = { diff --git a/test/fs/fs.h b/test/fs/fs.h index 400a7fbc..938600eb 100644 --- a/test/fs/fs.h +++ b/test/fs/fs.h @@ -31,6 +31,7 @@ typedef enum { FS_SETUP_FILE, FS_SETUP_DIR, FS_SETUP_SYMLINK, + FS_SETUP_DIR_SYMLINK, FS_SETUP_FIFO, } fs_setup_kind_t; @@ -54,7 +55,7 @@ typedef struct { static bool fs_setup_needs_symlinks(const fs_setup_t setup [FS_MAX_SETUP]) { sp_for(it, FS_MAX_SETUP) { if (!setup[it].path) break; - if (setup[it].kind == FS_SETUP_SYMLINK) return true; + if (setup[it].kind == FS_SETUP_SYMLINK || setup[it].kind == FS_SETUP_DIR_SYMLINK) return true; } return false; } @@ -127,8 +128,10 @@ static void fs_apply_setup(sp_test_t* t, sp_str_t sandbox, const fs_setup_t setu err = sp_fs_create_dir(path); break; } - case FS_SETUP_SYMLINK: { - err = sp_fs_create_sym_link(sp_cstr_as_str(ent->target), path); + case FS_SETUP_SYMLINK: + case FS_SETUP_DIR_SYMLINK: { + sp_fs_kind_t kind = ent->kind == FS_SETUP_DIR_SYMLINK ? SP_FS_KIND_DIR : SP_FS_KIND_FILE; + err = sp_fs_create_sym_link(sp_cstr_as_str(ent->target), path, kind); break; } case FS_SETUP_FIFO: { diff --git a/test/fs/it/enter.c b/test/fs/it/enter.c index fa70cece..04e5ede4 100644 --- a/test/fs/it/enter.c +++ b/test/fs/it/enter.c @@ -73,7 +73,7 @@ static const test_t tests [] = { .setup = { { "R/A", FS_SETUP_DIR }, { "R/A/B" }, - { .path = "R/L", .kind = FS_SETUP_SYMLINK, .target = "A" }, + { .path = "R/L", .kind = FS_SETUP_DIR_SYMLINK, .target = "A" }, }, .enter = { { "L", SP_ERR_SYS_LOOP } }, .expect = { diff --git a/test/fs/it/os.c b/test/fs/it/os.c index 37499256..8e20900b 100644 --- a/test/fs/it/os.c +++ b/test/fs/it/os.c @@ -105,7 +105,7 @@ static const test_t tests [] = { .setup = { { "R/A", FS_SETUP_DIR }, { "R/A/B" }, - { .path = "R/L", .kind = FS_SETUP_SYMLINK, .target = "A" }, + { .path = "R/L", .kind = FS_SETUP_DIR_SYMLINK, .target = "A" }, }, .recursive = true, .expect = { diff --git a/test/fs/links.c b/test/fs/links.c index 58ba3cb0..455baca2 100644 --- a/test/fs/links.c +++ b/test/fs/links.c @@ -14,6 +14,7 @@ typedef struct { const c8* name; fs_setup_t setup [FS_MAX_SETUP]; const c8* target; + sp_fs_kind_t kind; const c8* link; sp_err_t err; fs_expected_path_t expect [FS_MAX_PATHS]; @@ -75,6 +76,7 @@ static const sym_t sym_links [] = { { .path = "A", .content = "A" }, }, .target = "A", + .kind = SP_FS_KIND_FILE, .link = "L", .expect = { { .path = "A", .exists = true, .kind = SP_FS_KIND_FILE }, @@ -84,13 +86,25 @@ static const sym_t sym_links [] = { { .name = "directory", .setup = { - { "A", FS_SETUP_DIR }, + { .path = "A/F", .content = "F" }, }, .target = "A", + .kind = SP_FS_KIND_DIR, .link = "L", .expect = { { .path = "A", .exists = true, .kind = SP_FS_KIND_DIR }, { .path = "L", .exists = true, .kind = SP_FS_KIND_SYMLINK, .target = "A" }, + { .path = "L/F", .exists = true, .kind = SP_FS_KIND_FILE, .content = "F" }, + }, + }, + { + .name = "dangling", + .target = "A", + .kind = SP_FS_KIND_FILE, + .link = "L", + .expect = { + { .path = "A" }, + { .path = "L", .target = "A" }, }, }, { @@ -100,6 +114,7 @@ static const sym_t sym_links [] = { { .path = "B", .content = "B" }, }, .target = "A", + .kind = SP_FS_KIND_FILE, .link = "B", .err = SP_ERR_SYS_EXISTS, .expect = { @@ -144,7 +159,7 @@ sp_test_each(fs, sym_link, sym_t, sym_links) { sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, 0, &dir)); sp_path_t link = { .dir = dir, .sub = sp_cstr_as_str(it->link) }; - sp_expect_err_eq(t, sp_fs_create_sym_link_at(sp_cstr_as_str(it->target), link), it->err); + sp_expect_err_eq(t, sp_fs_create_sym_link_at(sp_cstr_as_str(it->target), link, it->kind), it->err); sp_sys_close(dir); fs_expect_paths(t, sandbox, it->expect); diff --git a/test/fs/predicates.c b/test/fs/predicates.c index b8a9359c..e90b209c 100644 --- a/test/fs/predicates.c +++ b/test/fs/predicates.c @@ -24,7 +24,7 @@ static const test_t tests [] = { { "A" }, { "B", FS_SETUP_DIR }, { .path = "C", .kind = FS_SETUP_SYMLINK, .target = "A" }, - { .path = "D", .kind = FS_SETUP_SYMLINK, .target = "B" }, + { .path = "D", .kind = FS_SETUP_DIR_SYMLINK, .target = "B" }, }, .probes = { { diff --git a/test/fs/remove/os.c b/test/fs/remove/os.c index b2ec7d4f..af247404 100644 --- a/test/fs/remove/os.c +++ b/test/fs/remove/os.c @@ -189,7 +189,7 @@ static const test_t tests [] = { { "B", FS_SETUP_DIR }, { "B/C" }, { "A", FS_SETUP_DIR }, - { .path = "A/L", .kind = FS_SETUP_SYMLINK, .target = "../B" }, + { .path = "A/L", .kind = FS_SETUP_DIR_SYMLINK, .target = "../B" }, { "A/D" }, }, .dir = true, @@ -209,7 +209,7 @@ static const test_t tests [] = { .setup = { { "A", FS_SETUP_DIR }, { "A/B" }, - { .path = "L", .kind = FS_SETUP_SYMLINK, .target = "A" }, + { .path = "L", .kind = FS_SETUP_DIR_SYMLINK, .target = "A" }, }, .dir = true, .path = "L", @@ -226,7 +226,7 @@ static const test_t tests [] = { .setup = { { "A", FS_SETUP_DIR }, { "A/B" }, - { .path = "L", .kind = FS_SETUP_SYMLINK, .target = "A" }, + { .path = "L", .kind = FS_SETUP_DIR_SYMLINK, .target = "A" }, }, .dir = true, .path = "L/", diff --git a/test/sys/harness.h b/test/sys/harness.h index e14e9f42..3012eb7e 100644 --- a/test/sys/harness.h +++ b/test/sys/harness.h @@ -15,6 +15,7 @@ typedef enum { SYS_SETUP_FILE, SYS_SETUP_DIR, SYS_SETUP_SYMLINK, + SYS_SETUP_DIR_SYMLINK, } sys_setup_kind_t; typedef struct { diff --git a/test/sys/open.c b/test/sys/open.c index 1a3fdd41..029a438f 100644 --- a/test/sys/open.c +++ b/test/sys/open.c @@ -261,7 +261,7 @@ static const sys_case_t sys_open_dir_cases [] = { .name = "follows_dir_symlink", .setup = { { .path = "dir", .kind = SYS_SETUP_DIR }, - { .path = "link", .kind = SYS_SETUP_SYMLINK, .target = "dir" }, + { .path = "link", .kind = SYS_SETUP_DIR_SYMLINK, .target = "dir" }, }, .steps = { { .kind = SYS_STEP_OPEN_DIR, .open_dir = { .path = "link" } }, @@ -271,7 +271,7 @@ static const sys_case_t sys_open_dir_cases [] = { .name = "nofollow_refuses_dir_symlink", .setup = { { .path = "dir", .kind = SYS_SETUP_DIR }, - { .path = "link", .kind = SYS_SETUP_SYMLINK, .target = "dir" }, + { .path = "link", .kind = SYS_SETUP_DIR_SYMLINK, .target = "dir" }, }, .steps = { { .kind = SYS_STEP_OPEN_DIR, .open_dir = { .path = "link", .flags = SP_SYS_OPEN_DIR_NO_FOLLOW, .err = SP_ERR_SYS_LOOP } }, diff --git a/test/sys/path.c b/test/sys/path.c index 0d938bd6..6734c021 100644 --- a/test/sys/path.c +++ b/test/sys/path.c @@ -32,6 +32,7 @@ typedef struct { const c8* dir; const c8* link; const c8* target; + bool dir_link; const c8* exists; const c8* not_exists; sp_fs_kind_t kind; @@ -87,6 +88,7 @@ static const test_t tests [] = { .dir = "dir", .link = "lnk", .target = "dir", + .dir_link = true, .exists = "dir", .not_exists = "lnk", }, @@ -116,6 +118,7 @@ static const test_t tests [] = { .dir = "dir", .link = "lnk", .target = "dir", + .dir_link = true, .exists = "lnk", }, { @@ -168,6 +171,7 @@ static const test_t slash_tests [] = { .dir = "dir", .link = "lnk", .target = "dir", + .dir_link = true, .exists = "dir", .not_exists = "lnk", }, @@ -209,6 +213,7 @@ static const test_t slash_tests [] = { .dir = "dir", .link = "lnk", .target = "dir", + .dir_link = true, .exists = "dir", }, { @@ -277,6 +282,7 @@ static const test_t slash_tests [] = { .dir = "dir", .link = "lnk", .target = "dir", + .dir_link = true, .kind = SP_FS_KIND_SYMLINK, }, { @@ -326,6 +332,7 @@ static const test_t slash_tests [] = { .dir = "dir", .link = "lnk", .target = "dir", + .dir_link = true, .kind = SP_FS_KIND_SYMLINK, }, }; @@ -378,7 +385,7 @@ static sp_err_t run(sp_test_t* t, test_t* c) { sp_str_t sandbox = sp_test_dir(t); if (c->file) sp_must_ok(t, sp_fs_create_file_str(sp_fs_join_path(mem, sandbox, sp_cstr_as_str(c->file)), sp_str_lit("A"))); if (c->dir) sp_must_ok(t, sp_fs_create_dir(sp_fs_join_path(mem, sandbox, sp_cstr_as_str(c->dir)))); - if (c->link) sp_must_ok(t, sp_fs_create_sym_link(sp_cstr_as_str(c->target), sp_fs_join_path(mem, sandbox, sp_cstr_as_str(c->link)))); + if (c->link) sp_must_ok(t, sp_fs_create_sym_link(sp_cstr_as_str(c->target), sp_fs_join_path(mem, sandbox, sp_cstr_as_str(c->link)), c->dir_link ? SP_FS_KIND_DIR : SP_FS_KIND_FILE)); sp_sys_fd_t sandbox_fd = SP_SYS_INVALID_FD; sp_try(sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, 0, &sandbox_fd)); diff --git a/test/sys/readlink.c b/test/sys/readlink.c index ab6bc79d..cadbb7d6 100644 --- a/test/sys/readlink.c +++ b/test/sys/readlink.c @@ -47,13 +47,13 @@ static sp_err_t run(sp_test_t* t, test_t* c) { } case SETUP_LINK: { sp_test_skip_without_symlinks(); - sp_must_ok(t, sp_fs_create_sym_link(sp_cstr_as_str(c->target), path)); + sp_must_ok(t, sp_fs_create_sym_link(sp_cstr_as_str(c->target), path, SP_FS_KIND_FILE)); break; } case SETUP_LINK_ABSOLUTE: { sp_test_skip_without_symlinks(); expected = sp_fs_join_path(mem, sandbox, sp_cstr_as_str(c->target)); - sp_must_ok(t, sp_fs_create_sym_link(expected, path)); + sp_must_ok(t, sp_fs_create_sym_link(expected, path, SP_FS_KIND_FILE)); break; } } diff --git a/test/sys/symlink.c b/test/sys/symlink.c index 3d5b2628..e43a390f 100644 --- a/test/sys/symlink.c +++ b/test/sys/symlink.c @@ -6,7 +6,7 @@ UTEST_F(sys_symlink, honors_dirfd) { run_sys_test(utest_result, (sys_test_t) { .label = "sys_symlink_honors_dirfd", .steps = { - { .kind = SYS_STEP_SYMLINK, .symlink = { .target = "target", .alias = "lnk" } }, + { .kind = SYS_STEP_SYMLINK, .symlink = { .target = "target", .alias = "lnk", .kind = SP_FS_KIND_FILE } }, { .kind = SYS_STEP_LSTAT, .lstat = { .path = "lnk" } }, }, }); @@ -19,9 +19,33 @@ UTEST_F(sys_symlink, resolves_target_relative_to_link) { { .path = "target.bin", .content = "hello" }, }, .steps = { - { .kind = SYS_STEP_SYMLINK, .symlink = { .target = "target.bin", .alias = "lnk" } }, + { .kind = SYS_STEP_SYMLINK, .symlink = { .target = "target.bin", .alias = "lnk", .kind = SP_FS_KIND_FILE } }, { .kind = SYS_STEP_OPEN, .open = { .path = "lnk" } }, { .kind = SYS_STEP_READ, .read = { .count = 8, .expect = "hello" } }, }, }); } + +UTEST_F(sys_symlink, dir_kind_resolves_to_dir) { + run_sys_test(utest_result, (sys_test_t) { + .label = "sys_symlink_dir_kind_resolves_to_dir", + .setup = { + { .path = "D", .kind = SYS_SETUP_DIR }, + }, + .steps = { + { .kind = SYS_STEP_SYMLINK, .symlink = { .target = "D", .alias = "L", .kind = SP_FS_KIND_DIR } }, + { .kind = SYS_STEP_STAT, .stat = { .path = "L", .kind = SP_FS_KIND_DIR } }, + }, + }); +} + +UTEST_F(sys_symlink, refuses_untyped_link) { + run_sys_test(utest_result, (sys_test_t) { + .label = "sys_symlink_refuses_untyped_link", + .steps = { + { .kind = SYS_STEP_SYMLINK, .symlink = { .target = "T", .alias = "L", .err = SP_ERR_SYS_INVALID } }, + { .kind = SYS_STEP_SYMLINK, .symlink = { .target = "T", .alias = "L", .kind = SP_FS_KIND_SYMLINK, .err = SP_ERR_SYS_INVALID } }, + { .kind = SYS_STEP_LSTAT, .lstat = { .path = "L", .err = SP_ERR_SYS_NOT_FOUND } }, + }, + }); +} diff --git a/test/sys/sys.c b/test/sys/sys.c index 61ea0f4a..40483bf9 100644 --- a/test/sys/sys.c +++ b/test/sys/sys.c @@ -3,7 +3,7 @@ static bool sys_case_wants_symlinks(sys_case_t* c) { sp_carr_for(c->setup, it) { if (!c->setup[it].path) break; - if (c->setup[it].kind == SYS_SETUP_SYMLINK) return true; + if (c->setup[it].kind == SYS_SETUP_SYMLINK || c->setup[it].kind == SYS_SETUP_DIR_SYMLINK) return true; } return false; } @@ -53,9 +53,11 @@ sp_err_t sys_case_run(sp_test_t* t, sys_case_t* c) { sp_fs_create_dir(path); break; } - case SYS_SETUP_SYMLINK: { + case SYS_SETUP_SYMLINK: + case SYS_SETUP_DIR_SYMLINK: { sp_str_t target = sp_cstr_as_str(ent->target); - if (sp_fs_create_sym_link(target, path)) { + sp_fs_kind_t kind = ent->kind == SYS_SETUP_DIR_SYMLINK ? SP_FS_KIND_DIR : SP_FS_KIND_FILE; + if (sp_fs_create_sym_link(target, path, kind)) { sp_test_fail(t, "failed to create symlink {} -> {}", sp_fmt_str(path), sp_fmt_str(target)); goto done; } diff --git a/test/sys/sys.h b/test/sys/sys.h index 8d6d65b0..bc520b5f 100644 --- a/test/sys/sys.h +++ b/test/sys/sys.h @@ -21,7 +21,7 @@ static void sys_probe_symlinks(sp_test_file_manager_t* fm) { sp_str_t target = sp_test_file_path_c(fm, ".sys_symlink_probe_target"); sp_str_t link = sp_test_file_path_c(fm, ".sys_symlink_probe_link"); sp_fs_create_file(target); - sys_symlinks_available = sp_fs_create_sym_link(target, link) == SP_OK; + sys_symlinks_available = sp_fs_create_sym_link(target, link, SP_FS_KIND_FILE) == SP_OK; if (sys_symlinks_available) sp_fs_remove_file(link); sp_fs_remove_file(target); } @@ -30,6 +30,7 @@ typedef enum { SYS_SETUP_FILE, SYS_SETUP_DIR, SYS_SETUP_SYMLINK, + SYS_SETUP_DIR_SYMLINK, SYS_SETUP_READONLY, } sys_setup_kind_t; @@ -80,7 +81,7 @@ typedef struct { struct { u32 slot; u64 count; u64 offset; const c8* expect; } pread; struct { u32 slot; const c8* data; sp_err_t err; } write; struct { u32 slot; const c8* data; u64 offset; } pwrite; - struct { const c8* target; const c8* alias; } symlink; + struct { const c8* target; const c8* alias; sp_fs_kind_t kind; sp_err_t err; } symlink; struct { const c8* from; sys_dir_t from_dir; const c8* to; sys_dir_t to_dir; sp_err_t err; } rename; struct { const c8* path; sp_fs_kind_t kind; sp_err_t err; } lstat; struct { const c8* path; sp_fs_kind_t kind; s64 size; sp_err_t err; } stat; @@ -108,7 +109,7 @@ typedef struct { static bool sys_test_wants_symlinks(sys_test_t* t) { sp_carr_for(t->setup, it) { if (!t->setup[it].path) break; - if (t->setup[it].kind == SYS_SETUP_SYMLINK) return true; + if (t->setup[it].kind == SYS_SETUP_SYMLINK || t->setup[it].kind == SYS_SETUP_DIR_SYMLINK) return true; } sp_carr_for(t->steps, it) { if (t->steps[it].kind == SYS_STEP_NONE) break; @@ -173,9 +174,11 @@ static bool sys_apply_setup(s32* utest_result, sp_test_file_manager_t* fm, sp_st sp_fs_create_dir(path); break; } - case SYS_SETUP_SYMLINK: { + case SYS_SETUP_SYMLINK: + case SYS_SETUP_DIR_SYMLINK: { sp_str_t target = sp_cstr_as_str(ent->target); - if (sp_fs_create_sym_link(target, path) != SP_OK) { + sp_fs_kind_t kind = ent->kind == SYS_SETUP_DIR_SYMLINK ? SP_FS_KIND_DIR : SP_FS_KIND_FILE; + if (sp_fs_create_sym_link(target, path, kind) != SP_OK) { SP_TEST_REPORT("failed to create symlink {} -> {}", sp_fmt_str(path), sp_fmt_str(target)); SP_FAIL(); return false; @@ -342,8 +345,8 @@ static void run_sys_test(s32* utest_result, sys_test_t t) { break; } case SYS_STEP_SYMLINK: { - sp_err_t err = sp_sys_symlink_s(sp_cstr_as_str(step->symlink.target), sandbox_fd, sp_cstr_as_str(step->symlink.alias)); - sys_expect_err(utest_result, "symlink", err, SP_OK); + sp_err_t err = sp_sys_symlink_s(sp_cstr_as_str(step->symlink.target), sandbox_fd, sp_cstr_as_str(step->symlink.alias), step->symlink.kind); + sys_expect_err(utest_result, "symlink", err, step->symlink.err); break; } case SYS_STEP_RENAME: { From df486a770f6e8060b05bbc5701060b3ad7c355de Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Tue, 29 Sep 2026 02:26:52 -0400 Subject: [PATCH 51/72] sp_path_t in tests --- test/fs/atomic.c | 8 ++-- test/fs/canonicalize.c | 6 +-- test/fs/collect.c | 4 +- test/fs/copy.c | 106 ++++++++++++++++++++--------------------- test/fs/create_dir.c | 26 +--------- test/fs/create_file.c | 8 +--- test/fs/dir/os.c | 10 ++-- test/fs/fd_relative.c | 14 +++--- test/fs/fs.h | 64 ++++++++++++------------- test/fs/get_cwd.c | 21 +++++--- test/fs/it/enter.c | 11 ++--- test/fs/it/os.c | 4 +- test/fs/links.c | 19 +++----- test/fs/mod_time.c | 16 ++----- test/fs/predicates.c | 24 +++++----- test/fs/remove/os.c | 32 +++++-------- test/fs/write_atomic.c | 12 ++--- test/sys/fd_path.c | 29 ++++++----- test/sys/path.c | 15 +++--- test/sys/perms.c | 31 ++++++------ test/sys/readlink.c | 15 +++--- test/sys/sys.c | 30 ++++++------ test/sys/timestamps.c | 14 +++--- test/sys/transfer.c | 18 ++++--- 24 files changed, 240 insertions(+), 297 deletions(-) diff --git a/test/fs/atomic.c b/test/fs/atomic.c index d39065e1..e5e40504 100644 --- a/test/fs/atomic.c +++ b/test/fs/atomic.c @@ -239,11 +239,11 @@ static sp_err_t drive(sp_fs_atomic_t* af, sp_err_t err, const test_t* it) { } sp_test_each(fs, atomic_open_at, test_t, open_at_tests) { - sp_str_t sandbox = sp_test_dir(t); + sp_path_t sandbox = sp_path_resolve(sp_test_dir(t)); fs_apply_setup(t, sandbox, it->setup); sp_sys_fd_t dir = SP_SYS_INVALID_FD; - sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, 0, &dir)); + sp_must_ok(t, sp_sys_open_dir_s(sandbox.dir, sandbox.sub, 0, &dir)); sp_fs_atomic_t af = sp_zero; sp_err_t err = drive(&af, sp_fs_atomic_open_at(&af, (sp_path_t) { .dir = dir, .sub = sp_str_view(it->path) }), it); @@ -256,11 +256,11 @@ sp_test_each(fs, atomic_open_at, test_t, open_at_tests) { } sp_test_each(fs, atomic_staged, test_t, staged_tests) { - sp_str_t sandbox = sp_test_dir(t); + sp_path_t sandbox = sp_path_resolve(sp_test_dir(t)); fs_apply_setup(t, sandbox, it->setup); sp_sys_fd_t dir = SP_SYS_INVALID_FD; - sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, 0, &dir)); + sp_must_ok(t, sp_sys_open_dir_s(sandbox.dir, sandbox.sub, 0, &dir)); sp_path_t path = { .dir = dir, .sub = sp_str_view(it->path) }; diff --git a/test/fs/canonicalize.c b/test/fs/canonicalize.c index 44c3e4f2..b4d5837b 100644 --- a/test/fs/canonicalize.c +++ b/test/fs/canonicalize.c @@ -161,11 +161,11 @@ sp_test_each(fs, canonicalize, test_t, tests) { if (fs_setup_needs_symlinks(it->setup)) sp_test_skip_without_symlinks(); sp_mem_t mem = sp_test_arena(t); - sp_str_t sandbox = sp_test_dir(t); + sp_path_t sandbox = sp_path_resolve(sp_test_dir(t)); fs_apply_setup(t, sandbox, it->setup); sp_sys_fd_t dir = SP_SYS_INVALID_FD; - sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, 0, &dir)); + sp_must_ok(t, sp_sys_open_dir_s(sandbox.dir, sandbox.sub, 0, &dir)); sp_path_t input = { .dir = dir, .sub = sp_str_view(it->input) }; sp_str_t result = sp_fs_canonicalize_path_at(mem, input); @@ -187,7 +187,7 @@ sp_test_each(fs, canonicalize, test_t, tests) { sp_expect_str_eq_c(t, sp_fs_get_name(result), it->expect.name); } if (it->expect.exists) { - sp_expect(t, sp_fs_exists(result)); + sp_expect(t, sp_fs_exists_at((sp_path_t) { .dir = dir, .sub = result })); } if (it->expect.idempotent) { sp_path_t again = { .dir = dir, .sub = result }; diff --git a/test/fs/collect.c b/test/fs/collect.c index f8631f08..97e194d3 100644 --- a/test/fs/collect.c +++ b/test/fs/collect.c @@ -55,9 +55,9 @@ static const test_t tests [] = { sp_test_each(fs, collect, test_t, tests) { sp_mem_t mem = sp_test_arena(t); - sp_str_t sandbox = sp_test_dir(t); + sp_path_t sandbox = sp_path_resolve(sp_test_dir(t)); fs_apply_setup(t, sandbox, it->setup); - sp_str_t root = sp_fs_join_path(mem, sandbox, sp_str_lit("R")); + sp_str_t root = sp_path_join(mem, sandbox, sp_str_lit("R")).sub; fs_match_t matches [FS_MAX_PATHS] = sp_zero; u32 n = 0; diff --git a/test/fs/copy.c b/test/fs/copy.c index ce878311..0a7cbae3 100644 --- a/test/fs/copy.c +++ b/test/fs/copy.c @@ -531,14 +531,16 @@ static const test_t tests [] = { sp_test_each(fs, copy, test_t, tests) { if (fs_setup_needs_symlinks(it->setup)) sp_test_skip_without_symlinks(); - sp_str_t sandbox = sp_test_dir(t); + sp_mem_t mem = sp_test_arena(t); + sp_path_t sandbox = sp_path_resolve(sp_test_dir(t)); fs_apply_setup(t, sandbox, it->setup); sp_sys_fd_t dir = SP_SYS_INVALID_FD; - sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, 0, &dir)); + sp_must_ok(t, sp_sys_open_dir_s(sandbox.dir, sandbox.sub, 0, &dir)); + sp_path_t dst_base = it->dst_dir ? sp_path_join(mem, sandbox, sp_cstr_as_str(it->dst_dir)) : sandbox; sp_sys_fd_t dst_dir = SP_SYS_INVALID_FD; - sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), fs_path_c(t, it->dst_dir ? it->dst_dir : ""), 0, &dst_dir)); + sp_must_ok(t, sp_sys_open_dir_s(dst_base.dir, dst_base.sub, 0, &dst_dir)); sp_path_t src = { .dir = dir, .sub = sp_cstr_as_str(it->src) }; sp_path_t dst = { .dir = dst_dir, .sub = sp_cstr_as_str(it->dst) }; @@ -572,11 +574,9 @@ static const size_test_t sizes [] = { sp_test_each(fs, copy_size, size_test_t, sizes) { sp_mem_t mem = sp_test_arena(t); - sp_sys_fd_t dir = SP_SYS_INVALID_FD; - sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sp_test_dir(t), 0, &dir)); - - sp_path_t from = { .dir = dir, .sub = sp_str_lit("A") }; - sp_path_t to = { .dir = dir, .sub = sp_str_lit("B") }; + sp_path_t sandbox = sp_path_resolve(sp_test_dir(t)); + sp_path_t from = sp_path_join(mem, sandbox, sp_str_lit("A")); + sp_path_t to = sp_path_join(mem, sandbox, sp_str_lit("B")); u8* data = sp_alloc_n(mem, u8, it->size); sp_for(i, it->size) { @@ -587,98 +587,98 @@ sp_test_each(fs, copy_size, size_test_t, sizes) { sp_str_t copied = sp_zero; sp_must_ok(t, sp_io_read_file_at(mem, to, &copied)); - sp_sys_close(dir); sp_must_eq(t, copied.len, it->size); sp_expect_mem_eq(t, copied.data, data, it->size); return SP_OK; } #if defined(SP_POSIX) -static sp_path_t at_root(sp_str_t path) { - return (sp_path_t) { .dir = sp_sys_get_root(0), .sub = path }; +static sp_sys_file_perms_t mode(u32 value) { + return (sp_sys_file_perms_t) { .value = value }; } // postcondition: dest has source's bytes and mode, on a fresh inode; timestamps unspecified sp_test(fs, copy_file_preserves_mode, .serial = true) { sp_mem_t mem = sp_test_arena(t); - sp_str_t from = fs_path_c(t, "A"); - sp_str_t to = fs_path_c(t, "B"); - sp_fs_create_file_str(from, sp_str_lit("A")); + sp_path_t sandbox = sp_path_resolve(sp_test_dir(t)); + sp_path_t from = sp_path_join(mem, sandbox, sp_str_lit("A")); + sp_path_t to = sp_path_join(mem, sandbox, sp_str_lit("B")); + sp_must_ok(t, sp_fs_create_file_str_at(from, sp_str_lit("A"))); + sp_must_ok(t, sp_sys_set_file_perms_s(from.dir, from.sub, mode(0755))); - sp_must_eq(t, chmod(sp_cstr_from_str(mem, from), 0755), 0); - - struct stat from_stat = sp_zero; - sp_must_eq(t, stat(sp_cstr_from_str(mem, from), &from_stat), 0); + sp_sys_file_meta_t from_meta = sp_zero; + sp_must_ok(t, sp_sys_get_path_metadata_s(from.dir, from.sub, &from_meta)); mode_t mask = umask(077); - sp_err_t copied_err = sp_fs_copy_file_at(at_root(from), at_root(to), SP_FS_ATOMIC_REPLACE); + sp_err_t copied_err = sp_fs_copy_file_at(from, to, SP_FS_ATOMIC_REPLACE); umask(mask); sp_must_ok(t, copied_err); - struct stat to_stat = sp_zero; - sp_must_eq(t, stat(sp_cstr_from_str(mem, to), &to_stat), 0); - sp_must_eq(t, to_stat.st_mode, from_stat.st_mode); - sp_must_eq(t, to_stat.st_size, from_stat.st_size); - sp_must(t, to_stat.st_ino != from_stat.st_ino); + sp_sys_file_meta_t to_meta = sp_zero; + sp_must_ok(t, sp_sys_get_path_metadata_s(to.dir, to.sub, &to_meta)); + sp_must_eq(t, to_meta.perms.value, from_meta.perms.value); + sp_must_eq(t, to_meta.size, from_meta.size); + sp_must(t, to_meta.id != from_meta.id); sp_str_t copied = sp_zero; - sp_io_read_file(mem, to, &copied); + sp_io_read_file_at(mem, to, &copied); sp_expect_str_eq(t, copied, sp_str_lit("A")); return SP_OK; } sp_test(fs, copy_file_replaces_read_only_dest) { sp_mem_t mem = sp_test_arena(t); - sp_str_t from = fs_path_c(t, "A"); - sp_str_t to = fs_path_c(t, "B"); - sp_fs_create_file_str(from, sp_str_lit("A")); - sp_fs_create_file_str(to, sp_str_lit("B")); - sp_must_eq(t, chmod(sp_cstr_from_str(mem, to), 0444), 0); + sp_path_t sandbox = sp_path_resolve(sp_test_dir(t)); + sp_path_t from = sp_path_join(mem, sandbox, sp_str_lit("A")); + sp_path_t to = sp_path_join(mem, sandbox, sp_str_lit("B")); + sp_must_ok(t, sp_fs_create_file_str_at(from, sp_str_lit("A"))); + sp_must_ok(t, sp_fs_create_file_str_at(to, sp_str_lit("B"))); + sp_must_ok(t, sp_sys_set_file_perms_s(to.dir, to.sub, mode(0444))); - sp_must_ok(t, sp_fs_copy_file_at(at_root(from), at_root(to), SP_FS_ATOMIC_REPLACE)); + sp_must_ok(t, sp_fs_copy_file_at(from, to, SP_FS_ATOMIC_REPLACE)); sp_str_t copied = sp_zero; - sp_io_read_file(mem, to, &copied); + sp_io_read_file_at(mem, to, &copied); sp_expect_str_eq(t, copied, sp_str_lit("A")); return SP_OK; } sp_test(fs, copy_file_unwritable_parent_leaves_nothing) { sp_mem_t mem = sp_test_arena(t); - sp_str_t from = fs_path_c(t, "A"); - sp_str_t dir = fs_path_c(t, "D"); - sp_str_t to = fs_path_c(t, "D/B"); - sp_fs_create_file_str(from, sp_str_lit("A")); - sp_must_ok(t, sp_fs_create_dir(dir)); + sp_path_t sandbox = sp_path_resolve(sp_test_dir(t)); + sp_path_t from = sp_path_join(mem, sandbox, sp_str_lit("A")); + sp_path_t dir = sp_path_join(mem, sandbox, sp_str_lit("D")); + sp_path_t to = sp_path_join(mem, sandbox, sp_str_lit("D/B")); + sp_must_ok(t, sp_fs_create_file_str_at(from, sp_str_lit("A"))); + sp_must_ok(t, sp_fs_create_dir_at(dir)); - const c8* dir_c = sp_cstr_from_str(mem, dir); - sp_must_eq(t, chmod(dir_c, 0555), 0); - sp_err_t result = sp_fs_copy_file_at(at_root(from), at_root(to), SP_FS_ATOMIC_REPLACE); - sp_must_eq(t, chmod(dir_c, 0755), 0); + sp_must_ok(t, sp_sys_set_file_perms_s(dir.dir, dir.sub, mode(0555))); + sp_err_t result = sp_fs_copy_file_at(from, to, SP_FS_ATOMIC_REPLACE); + sp_must_ok(t, sp_sys_set_file_perms_s(dir.dir, dir.sub, mode(0755))); if (!result) return sp_test_skip(t, "directory permissions not enforced"); sp_expect_err_eq(t, result, SP_ERR_SYS_ACCESS_DENIED); - sp_expect(t, !sp_fs_exists(to)); - fs_expect_no_temps(t, sp_test_dir(t)); + sp_expect(t, !sp_fs_exists_at(to)); + fs_expect_no_temps(t, sandbox); return SP_OK; } sp_test(fs, copy_tree_unreadable_subdir_fails) { sp_mem_t mem = sp_test_arena(t); - sp_str_t from = fs_path_c(t, "A"); - sp_str_t locked = fs_path_c(t, "A/B"); - sp_str_t to = fs_path_c(t, "E"); - sp_must_ok(t, sp_fs_create_dir(locked)); - sp_must_ok(t, sp_fs_create_file_str(fs_path_c(t, "A/B/C"), sp_str_lit("C"))); + sp_path_t sandbox = sp_path_resolve(sp_test_dir(t)); + sp_path_t from = sp_path_join(mem, sandbox, sp_str_lit("A")); + sp_path_t locked = sp_path_join(mem, sandbox, sp_str_lit("A/B")); + sp_path_t to = sp_path_join(mem, sandbox, sp_str_lit("E")); + sp_must_ok(t, sp_fs_create_dir_at(locked)); + sp_must_ok(t, sp_fs_create_file_str_at(sp_path_join(mem, sandbox, sp_str_lit("A/B/C")), sp_str_lit("C"))); - const c8* locked_c = sp_cstr_from_str(mem, locked); - sp_must_eq(t, chmod(locked_c, 0), 0); - sp_err_t result = sp_fs_copy_tree_at(at_root(from), at_root(to), SP_FS_ATOMIC_REPLACE); - sp_must_eq(t, chmod(locked_c, 0755), 0); + sp_must_ok(t, sp_sys_set_file_perms_s(locked.dir, locked.sub, mode(0))); + sp_err_t result = sp_fs_copy_tree_at(from, to, SP_FS_ATOMIC_REPLACE); + sp_must_ok(t, sp_sys_set_file_perms_s(locked.dir, locked.sub, mode(0755))); if (!result) return sp_test_skip(t, "directory permissions not enforced"); sp_expect_err_eq(t, result, SP_ERR_SYS_ACCESS_DENIED); - sp_expect(t, !sp_fs_exists(fs_path_c(t, "E/B/C"))); + sp_expect(t, !sp_fs_exists_at(sp_path_join(mem, sandbox, sp_str_lit("E/B/C")))); return SP_OK; } #endif diff --git a/test/fs/create_dir.c b/test/fs/create_dir.c index 1bcd8df3..26b3157c 100644 --- a/test/fs/create_dir.c +++ b/test/fs/create_dir.c @@ -133,34 +133,12 @@ static const test_t tests [] = { sp_test_each(fs, create_dir, test_t, tests) { if (fs_setup_needs_symlinks(it->setup)) sp_test_skip_without_symlinks(); - sp_str_t sandbox = sp_test_dir(t); + sp_path_t sandbox = sp_path_resolve(sp_test_dir(t)); fs_apply_setup(t, sandbox, it->setup); - sp_sys_fd_t dir = SP_SYS_INVALID_FD; - sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, 0, &dir)); - - sp_path_t target = { dir, sp_cstr_as_str(it->target) }; + sp_path_t target = sp_path_join(sp_test_arena(t), sandbox, sp_cstr_as_str(it->target)); sp_expect_err_eq(t, sp_fs_create_dir_at(target), it->expect.err); - sp_sys_close(dir); fs_expect_paths(t, sandbox, it->expect.paths); return SP_OK; } - -sp_test(fs, create_dir_create_multi_level_relative, .serial = true) { - sp_test_skip_on_wasm() - - sp_mem_t mem = sp_test_arena(t); - sp_str_t sandbox = sp_test_dir(t); - - sp_str_t cwd = sp_fs_get_cwd_path(mem); - sp_must_ok(t, sp_sys_chdir_s(sandbox)); - sp_err_t result = sp_fs_create_dir(sp_str_lit("A/B")); - sp_must_ok(t, sp_sys_chdir_s(cwd)); - - sp_expect_ok(t, result); - sp_str_t created = sp_fs_join_path(mem, sandbox, sp_str_lit("A/B")); - sp_expect(t, sp_fs_exists(created)); - sp_expect_eq(t, sp_fs_get_kind(created), SP_FS_KIND_DIR); - return SP_OK; -} diff --git a/test/fs/create_file.c b/test/fs/create_file.c index 930aa108..6f60ae25 100644 --- a/test/fs/create_file.c +++ b/test/fs/create_file.c @@ -71,13 +71,10 @@ static const test_t tests [] = { }; sp_test_each(fs, create_file, test_t, tests) { - sp_str_t sandbox = sp_test_dir(t); + sp_path_t sandbox = sp_path_resolve(sp_test_dir(t)); fs_apply_setup(t, sandbox, it->setup); - sp_sys_fd_t dir = SP_SYS_INVALID_FD; - sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, 0, &dir)); - - sp_path_t path = { .dir = dir, .sub = sp_str_view(it->path) }; + sp_path_t path = sp_path_join(sp_test_arena(t), sandbox, sp_str_view(it->path)); sp_err_t result = SP_OK; switch (it->variant) { case VARIANT_EMPTY: { @@ -97,7 +94,6 @@ sp_test_each(fs, create_file, test_t, tests) { break; } } - sp_sys_close(dir); sp_expect_ok(t, result); fs_expect_paths(t, sandbox, it->expect); diff --git a/test/fs/dir/os.c b/test/fs/dir/os.c index 4f254a6e..2132c613 100644 --- a/test/fs/dir/os.c +++ b/test/fs/dir/os.c @@ -91,10 +91,10 @@ sp_test_each(fs, dir, test_t, tests) { if (fs_setup_needs_symlinks(it->setup)) sp_test_skip_without_symlinks(); sp_mem_t mem = sp_test_arena(t); - sp_str_t sandbox = sp_test_dir(t); + sp_path_t sandbox = sp_path_resolve(sp_test_dir(t)); fs_apply_setup(t, sandbox, it->setup); - sp_str_t dir = it->dir ? sp_fs_join_path(mem, sandbox, sp_cstr_as_str(it->dir)) : sandbox; + sp_path_t dir = it->dir ? sp_path_join(mem, sandbox, sp_cstr_as_str(it->dir)) : sandbox; fs_match_t* matches = sp_alloc_n(mem, fs_match_t, FS_MAX_PATHS + it->bulk); u32 n = 0; @@ -104,20 +104,20 @@ sp_test_each(fs, dir, test_t, tests) { } sp_for(b, it->bulk) { sp_str_t name = sp_fmt(mem, "{}{}", sp_fmt_cstr(BULK_PREFIX), sp_fmt_uint(b)).value; - sp_expect_ok(t, sp_fs_create_file(sp_fs_join_path(mem, dir, name))); + sp_expect_ok(t, sp_fs_create_file_at(sp_path_join(mem, dir, name))); matches[n++] = (fs_match_t) { .key = name, .kind = SP_FS_KIND_FILE }; } sp_sys_fd_t sandbox_fd = SP_SYS_INVALID_FD; if (it->relative) { - sp_try(sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, 0, &sandbox_fd)); + sp_try(sp_sys_open_dir_s(sandbox.dir, sandbox.sub, 0, &sandbox_fd)); } SP_ALIGNED u8 buf [SP_SYS_DIR_MIN_BUF]; sp_fs_dir_t iter = sp_zero; sp_err_t open_err = it->relative ? sp_fs_dir_open(&iter, sandbox_fd, sp_cstr_as_str(it->dir), 0, sp_mem_slice(buf, sizeof(buf))) - : sp_fs_dir_open(&iter, sp_sys_get_root(0), dir, 0, sp_mem_slice(buf, sizeof(buf))); + : sp_fs_dir_open(&iter, dir.dir, dir.sub, 0, sp_mem_slice(buf, sizeof(buf))); sp_expect_ok(t, open_err); if (!open_err) { diff --git a/test/fs/fd_relative.c b/test/fs/fd_relative.c index 7450f58c..2dbc3106 100644 --- a/test/fs/fd_relative.c +++ b/test/fs/fd_relative.c @@ -109,17 +109,17 @@ static const test_t tests [] = { sp_test_each(fs, fd_relative, test_t, tests) { #if !defined(SP_WIN32) if (it->win32) return sp_test_skip(t, "windows only"); +#endif +#if defined(SP_WASM) + if (it->op == OP_OPEN_ABS) return sp_test_skip(t, "no absolute names on wasm"); #endif sp_mem_t mem = sp_test_arena(t); - sp_str_t sandbox = sp_test_dir(t); + sp_path_t sandbox = sp_path_resolve(sp_test_dir(t)); fs_apply_setup(t, sandbox, it->setup); - sp_str_t cwd_path = sp_fs_join_path(mem, sandbox, sp_str_view(it->cwd)); + sp_path_t cwd_path = sp_path_join(mem, sandbox, sp_str_view(it->cwd)); sp_sys_fd_t cwd = SP_SYS_INVALID_FD; - if (sp_sys_open_dir_s(sp_sys_get_root(0), cwd_path, 0, &cwd) != SP_OK) { - sp_test_fail(t, "failed to open dir {}", sp_fmt_str(cwd_path)); - return SP_ERR; - } + sp_must_ok(t, sp_sys_open_dir_s(cwd_path.dir, cwd_path.sub, 0, &cwd)); switch (it->op) { case OP_STAT: { @@ -130,7 +130,7 @@ sp_test_each(fs, fd_relative, test_t, tests) { case OP_OPEN: case OP_OPEN_ABS: { sp_str_t path = it->op == OP_OPEN_ABS - ? sp_fs_join_path(mem, sandbox, sp_str_view(it->path)) + ? sp_fs_canonicalize_path_at(mem, sp_path_join(mem, sandbox, sp_str_view(it->path))) : sp_str_view(it->path); sp_sys_fd_t fd = SP_SYS_INVALID_FD; sp_expect_ok(t, sp_sys_open_s(cwd, path, SP_SYS_OPEN_MODE_RO, 0, &fd)); diff --git a/test/fs/fs.h b/test/fs/fs.h index 938600eb..b2ee13a2 100644 --- a/test/fs/fs.h +++ b/test/fs/fs.h @@ -12,18 +12,6 @@ #define FS_MAX_PATHS 8 -///////////// -// SANDBOX // -///////////// -static sp_str_t fs_path(sp_test_t* t, sp_str_t relative) { - return sp_fs_join_path(sp_test_arena(t), sp_test_dir(t), relative); -} - -static sp_str_t fs_path_c(sp_test_t* t, const c8* relative) { - return fs_path(t, sp_cstr_as_str(relative)); -} - - //////////////////// // SHARED HARNESS // //////////////////// @@ -60,6 +48,10 @@ static bool fs_setup_needs_symlinks(const fs_setup_t setup [FS_MAX_SETUP]) { return false; } +static sp_path_t fs_parent(sp_path_t path) { + return (sp_path_t) { .dir = path.dir, .sub = sp_fs_parent_path(path.sub) }; +} + static void fs_expect_bool(sp_test_t* t, sp_str_t path, const c8* label, bool actual, bool expected) { if (actual == expected) return; @@ -109,78 +101,82 @@ static void fs_match_finish(sp_test_t* t, fs_match_t* matches, u32 n) { } } -static void fs_apply_setup(sp_test_t* t, sp_str_t sandbox, const fs_setup_t setup [FS_MAX_SETUP]) { +static void fs_apply_setup(sp_test_t* t, sp_path_t sandbox, const fs_setup_t setup [FS_MAX_SETUP]) { sp_mem_t mem = sp_test_arena(t); sp_for(it, FS_MAX_SETUP) { const fs_setup_t* ent = &setup[it]; if (!ent->path) break; - sp_str_t path = sp_fs_join_path(mem, sandbox, sp_cstr_as_str(ent->path)); - sp_fs_create_dir(sp_fs_parent_path(path)); + sp_path_t path = sp_path_join(mem, sandbox, sp_cstr_as_str(ent->path)); + sp_fs_create_dir_at(fs_parent(path)); sp_err_t err = SP_OK; switch (ent->kind) { case FS_SETUP_FILE: { - err = sp_fs_create_file_str(path, ent->content ? sp_cstr_as_str(ent->content) : sp_str_lit("")); + err = sp_fs_create_file_str_at(path, ent->content ? sp_cstr_as_str(ent->content) : sp_str_lit("")); break; } case FS_SETUP_DIR: { - err = sp_fs_create_dir(path); + err = sp_fs_create_dir_at(path); break; } case FS_SETUP_SYMLINK: case FS_SETUP_DIR_SYMLINK: { sp_fs_kind_t kind = ent->kind == FS_SETUP_DIR_SYMLINK ? SP_FS_KIND_DIR : SP_FS_KIND_FILE; - err = sp_fs_create_sym_link(sp_cstr_as_str(ent->target), path, kind); + err = sp_fs_create_sym_link_at(sp_cstr_as_str(ent->target), path, kind); break; } case FS_SETUP_FIFO: { #if defined(SP_POSIX) - err = mkfifo(sp_cstr_from_str(mem, path), 0644) ? SP_ERR_SYS : SP_OK; + sp_str_t parent = sp_fs_canonicalize_path_at(mem, fs_parent(path)); + sp_str_t fifo = sp_fs_join_path(mem, parent, sp_fs_get_name(path.sub)); + err = mkfifo(sp_cstr_from_str(mem, fifo), 0644) ? SP_ERR_SYS : SP_OK; #else err = SP_ERR_SYS_UNSUPPORTED; #endif break; } } - if (err) sp_test_fail(t, "failed to create {}: {}", sp_fmt_str(path), sp_fmt_str(sp_test_err_str(t, err))); + if (err) sp_test_fail(t, "failed to create {}: {}", sp_fmt_cstr(ent->path), sp_fmt_str(sp_test_err_str(t, err))); } } -static void fs_expect_no_temps(sp_test_t* t, sp_str_t sandbox) { - sp_da(sp_fs_entry_t) entries = sp_zero; - sp_expect_ok(t, sp_fs_collect_recursive(sp_test_arena(t), sandbox, &entries)); - sp_da_for(entries, it) { - if (sp_str_ends_with(entries[it].name, sp_str_lit(".tmp"))) { - sp_test_fail(t, "temp file left behind: {}", sp_fmt_str(entries[it].path)); +static void fs_expect_no_temps(sp_test_t* t, sp_path_t sandbox) { + sp_fs_it_t walk = sp_fs_it_new_at(sp_test_arena(t), sandbox, 0); + while (sp_fs_it_walk(&walk)) { + if (sp_str_ends_with(walk.entry.name, sp_str_lit(".tmp"))) { + sp_test_fail(t, "temp file left behind: {}", sp_fmt_str(walk.entry.rel)); } } + sp_expect_ok(t, walk.err); + sp_fs_it_deinit(&walk); } -static void fs_expect_paths(sp_test_t* t, sp_str_t sandbox, const fs_expected_path_t expected [FS_MAX_PATHS]) { +static void fs_expect_paths(sp_test_t* t, sp_path_t sandbox, const fs_expected_path_t expected [FS_MAX_PATHS]) { sp_mem_t mem = sp_test_arena(t); sp_for(it, FS_MAX_PATHS) { const fs_expected_path_t* info = &expected[it]; if (!info->path) break; - sp_str_t path = sp_fs_join_path(mem, sandbox, sp_cstr_as_str(info->path)); + sp_str_t label = sp_cstr_as_str(info->path); + sp_path_t path = sp_path_join(mem, sandbox, label); - bool exists = sp_fs_exists(path); + bool exists = sp_fs_exists_at(path); if (exists != info->exists) { - sp_test_fail(t, "expected {} {} exist", sp_fmt_str(path), sp_fmt_cstr(info->exists ? "to" : "not to")); + sp_test_fail(t, "expected {} {} exist", sp_fmt_str(label), sp_fmt_cstr(info->exists ? "to" : "not to")); } if (info->exists) { - fs_expect_kind(t, path, sp_fs_get_kind(path), info->kind); + fs_expect_kind(t, label, sp_fs_get_kind_at(path), info->kind); } if (info->content) { sp_str_t actual = sp_zero; - sp_io_read_file(mem, path, &actual); + sp_io_read_file_at(mem, path, &actual); sp_str_t content = sp_cstr_as_str(info->content); if (!sp_str_equal(actual, content)) { sp_test_record(t, (sp_test_failure_t) { - .message = sp_test_format(t, "content of {}", sp_fmt_str(path)), + .message = sp_test_format(t, "content of {}", sp_fmt_str(label)), .expected = sp_test_format(t, "{.quote}", sp_fmt_str(content)), .actual = sp_test_format(t, "{.quote}", sp_fmt_str(actual)), }); @@ -190,7 +186,7 @@ static void fs_expect_paths(sp_test_t* t, sp_str_t sandbox, const fs_expected_pa if (info->target) { c8 buf [SP_PATH_MAX]; sp_str_t actual = sp_zero; - sp_expect_ok(t, sp_sys_readlink_s(sp_sys_get_root(0), path, buf, sizeof(buf), &actual)); + sp_expect_ok(t, sp_sys_readlink_s(path.dir, path.sub, buf, sizeof(buf), &actual)); sp_expect_str_eq_c(t, sp_fs_normalize_path(mem, actual), info->target); } } diff --git a/test/fs/get_cwd.c b/test/fs/get_cwd.c index 4f8f622c..ecdafeff 100644 --- a/test/fs/get_cwd.c +++ b/test/fs/get_cwd.c @@ -19,15 +19,22 @@ sp_test(fs, get_cwd_unlinked_cwd_has_no_path, .serial = true) { sp_str_t original = sp_fs_get_cwd_path(mem); sp_must_gt(t, original.len, 0); - sp_str_t sandbox = sp_test_dir(t); - sp_must_ok(t, sp_sys_chdir_s(sandbox)); - sp_must_ok(t, sp_fs_remove_dir(sandbox)); + sp_path_t sandbox = sp_path_resolve(sp_test_dir(t)); + sp_sys_fd_t dir = SP_SYS_INVALID_FD; + sp_must_ok(t, sp_sys_open_dir_s(sandbox.dir, sandbox.sub, 0, &dir)); + + sp_path_t doomed = { .dir = dir, .sub = sp_str_lit("A") }; + sp_err_t err = sp_fs_create_dir_at(doomed); + if (!err) err = sp_sys_chdir_s(sp_fs_canonicalize_path_at(mem, doomed)); + if (!err) err = sp_fs_remove_dir_at(doomed); sp_str_t cwd = sp_fs_get_cwd_path(mem); // restore cwd before any assertion can fail, so other tests aren't poisoned sp_sys_chdir_s(original); + sp_sys_close(dir); + sp_must_ok(t, err); sp_must(t, sp_str_empty(cwd)); return SP_OK; #endif @@ -42,12 +49,12 @@ sp_test(fs, get_cwd_is_root_zero) { sp_test(fs, path_at_cwd_passes_through) { sp_test_skip_on_wasm() - sp_str_t file = sp_fs_join_path(sp_test_arena(t), sp_test_dir(t), sp_str_lit("A")); - sp_must_ok(t, sp_fs_create_file(file)); + sp_path_t file = sp_path_join(sp_test_arena(t), sp_path_resolve(sp_test_dir(t)), sp_str_lit("A")); + sp_must_ok(t, sp_fs_create_file_at(file)); - sp_path_t path = sp_path_at_cwd(file); + sp_path_t path = sp_path_at_cwd(file.sub); sp_expect_eq(t, path.dir, sp_fs_get_cwd()); - sp_expect_str_eq(t, path.sub, file); + sp_expect_str_eq(t, path.sub, file.sub); sp_expect(t, sp_fs_is_file_at(path)); sp_expect(t, sp_fs_is_dir_at(sp_path_at_cwd(sp_str_lit(".")))); return SP_OK; diff --git a/test/fs/it/enter.c b/test/fs/it/enter.c index 04e5ede4..df11f443 100644 --- a/test/fs/it/enter.c +++ b/test/fs/it/enter.c @@ -112,13 +112,11 @@ sp_test_each(fs, it_enter, test_t, tests) { if (fs_setup_needs_symlinks(it->setup)) sp_test_skip_without_symlinks(); sp_mem_t mem = sp_test_arena(t); - sp_str_t sandbox = sp_test_dir(t); + sp_path_t sandbox = sp_path_resolve(sp_test_dir(t)); fs_apply_setup(t, sandbox, it->setup); - sp_sys_fd_t dir = SP_SYS_INVALID_FD; - sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, 0, &dir)); - - sp_str_t base = sp_str_lit("R"); + sp_path_t root = sp_path_join(mem, sandbox, sp_str_lit("R")); + sp_str_t base = root.sub; fs_match_t entries [FS_MAX_PATHS] = sp_zero; u32 num_entries = 0; sp_carr_for(it->expect.entries, e) { @@ -132,7 +130,7 @@ sp_test_each(fs, it_enter, test_t, tests) { sp_str_t entered [FS_MAX_PATHS] = sp_zero; u32 depth = 0; - sp_fs_it_t walk = sp_fs_it_new_at(mem, (sp_path_t) { .dir = dir, .sub = base }, 0); + sp_fs_it_t walk = sp_fs_it_new_at(mem, root, 0); while (sp_fs_it_next(&walk)) { sp_expect_str_eq(t, sp_fs_join_path(mem, base, walk.entry.rel), walk.entry.path); switch (walk.yield) { @@ -160,7 +158,6 @@ sp_test_each(fs, it_enter, test_t, tests) { sp_expect_ok(t, walk.err); sp_expect_eq(t, depth, (u32)0); sp_fs_it_deinit(&walk); - sp_sys_close(dir); fs_match_finish(t, entries, num_entries); fs_match_finish(t, leaves, num_leaves); diff --git a/test/fs/it/os.c b/test/fs/it/os.c index 8e20900b..2afdf7a6 100644 --- a/test/fs/it/os.c +++ b/test/fs/it/os.c @@ -164,11 +164,11 @@ sp_test_each(fs, it, test_t, tests) { if (fs_setup_needs_symlinks(it->setup)) sp_test_skip_without_symlinks(); sp_mem_t mem = sp_test_arena(t); - sp_str_t sandbox = sp_test_dir(t); + sp_path_t sandbox = sp_path_resolve(sp_test_dir(t)); fs_apply_setup(t, sandbox, it->setup); sp_sys_fd_t dir = SP_SYS_INVALID_FD; - sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, 0, &dir)); + sp_must_ok(t, sp_sys_open_dir_s(sandbox.dir, sandbox.sub, 0, &dir)); sp_str_t base = sp_str_lit("R"); sp_path_t root = { .dir = dir, .sub = sp_cstr_as_str(it->root ? it->root : "R") }; diff --git a/test/fs/links.c b/test/fs/links.c index 455baca2..6f108f20 100644 --- a/test/fs/links.c +++ b/test/fs/links.c @@ -125,14 +125,12 @@ static const sym_t sym_links [] = { }; sp_test_each(fs, hard_link, hard_t, hard_links) { - sp_str_t sandbox = sp_test_dir(t); + sp_mem_t mem = sp_test_arena(t); + sp_path_t sandbox = sp_path_resolve(sp_test_dir(t)); fs_apply_setup(t, sandbox, it->setup); - sp_sys_fd_t dir = SP_SYS_INVALID_FD; - sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, 0, &dir)); - - sp_path_t target = { .dir = dir, .sub = sp_cstr_as_str(it->target) }; - sp_path_t link = { .dir = dir, .sub = sp_cstr_as_str(it->link) }; + sp_path_t target = sp_path_join(mem, sandbox, sp_cstr_as_str(it->target)); + sp_path_t link = sp_path_join(mem, sandbox, sp_cstr_as_str(it->link)); sp_expect_err_eq(t, sp_fs_create_hard_link_at(target, link), it->err); // a hard link shares content with its target: rewrite the target through @@ -143,7 +141,6 @@ sp_test_each(fs, hard_link, hard_t, hard_links) { sp_io_write_str(&writer.base, sp_cstr_as_str(it->rewrite), SP_NULLPTR); sp_io_file_writer_close(&writer); } - sp_sys_close(dir); fs_expect_paths(t, sandbox, it->expect); return SP_OK; @@ -152,15 +149,11 @@ sp_test_each(fs, hard_link, hard_t, hard_links) { sp_test_each(fs, sym_link, sym_t, sym_links) { sp_test_skip_without_symlinks(); - sp_str_t sandbox = sp_test_dir(t); + sp_path_t sandbox = sp_path_resolve(sp_test_dir(t)); fs_apply_setup(t, sandbox, it->setup); - sp_sys_fd_t dir = SP_SYS_INVALID_FD; - sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, 0, &dir)); - - sp_path_t link = { .dir = dir, .sub = sp_cstr_as_str(it->link) }; + sp_path_t link = sp_path_join(sp_test_arena(t), sandbox, sp_cstr_as_str(it->link)); sp_expect_err_eq(t, sp_fs_create_sym_link_at(sp_cstr_as_str(it->target), link, it->kind), it->err); - sp_sys_close(dir); fs_expect_paths(t, sandbox, it->expect); return SP_OK; diff --git a/test/fs/mod_time.c b/test/fs/mod_time.c index ff96bcf8..9d4bd99e 100644 --- a/test/fs/mod_time.c +++ b/test/fs/mod_time.c @@ -1,25 +1,20 @@ #include "fs.h" sp_test(fs, mod_time_nonzero) { - sp_sys_fd_t sandbox = SP_SYS_INVALID_FD; - sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sp_test_dir(t), 0, &sandbox)); - - sp_path_t file = { .dir = sandbox, .sub = sp_str_lit("A") }; - sp_path_t dir = { .dir = sandbox, .sub = sp_str_lit("B") }; + sp_mem_t mem = sp_test_arena(t); + sp_path_t sandbox = sp_path_resolve(sp_test_dir(t)); + sp_path_t file = sp_path_join(mem, sandbox, sp_str_lit("A")); + sp_path_t dir = sp_path_join(mem, sandbox, sp_str_lit("B")); sp_fs_create_file_at(file); sp_fs_create_dir_at(dir); sp_expect(t, sp_fs_get_mod_time_at(file).s > 0); sp_expect(t, sp_fs_get_mod_time_at(dir).s > 0); - sp_sys_close(sandbox); return SP_OK; } sp_test(fs, mod_time_updates_after_write) { - sp_sys_fd_t sandbox = SP_SYS_INVALID_FD; - sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sp_test_dir(t), 0, &sandbox)); - - sp_path_t file = { sandbox, sp_str_lit("A") }; + sp_path_t file = sp_path_join(sp_test_arena(t), sp_path_resolve(sp_test_dir(t)), sp_str_lit("A")); sp_fs_create_file_str_at(file, sp_str_lit("A")); sp_tm_epoch_t before = sp_fs_get_mod_time_at(file); @@ -31,7 +26,6 @@ sp_test(fs, mod_time_updates_after_write) { sp_io_file_writer_close(&writer); sp_tm_epoch_t after = sp_fs_get_mod_time_at(file); - sp_sys_close(sandbox); sp_must(t, after.s > before.s || (after.s == before.s && after.ns > before.ns)); return SP_OK; } diff --git a/test/fs/predicates.c b/test/fs/predicates.c index e90b209c..ac633d2d 100644 --- a/test/fs/predicates.c +++ b/test/fs/predicates.c @@ -91,26 +91,24 @@ static const test_t tests [] = { sp_test_each(fs, predicates, test_t, tests) { if (fs_setup_needs_symlinks(it->setup)) sp_test_skip_without_symlinks(); - sp_str_t sandbox = sp_test_dir(t); + sp_mem_t mem = sp_test_arena(t); + sp_path_t sandbox = sp_path_resolve(sp_test_dir(t)); fs_apply_setup(t, sandbox, it->setup); - sp_sys_fd_t dir = SP_SYS_INVALID_FD; - sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, 0, &dir)); - sp_carr_for(it->probes, i) { const probe_t* probe = &it->probes[i]; if (!probe->path) break; - sp_path_t path = { dir, sp_cstr_as_str(probe->path) }; + sp_str_t label = sp_cstr_as_str(probe->path); + sp_path_t path = sp_path_join(mem, sandbox, label); - fs_expect_bool(t, path.sub, "exists", sp_fs_exists_at(path), probe->exists); - fs_expect_bool(t, path.sub, "is_regular_file", sp_fs_is_file_at(path), probe->file); - fs_expect_bool(t, path.sub, "is_dir", sp_fs_is_dir_at(path), probe->dir); - fs_expect_bool(t, path.sub, "is_symlink", sp_fs_is_symlink_at(path), probe->symlink); - fs_expect_bool(t, path.sub, "is_target_regular_file", sp_fs_is_target_file_at(path), probe->target_file); - fs_expect_bool(t, path.sub, "is_target_dir", sp_fs_is_target_dir_at(path), probe->target_dir); - fs_expect_kind(t, path.sub, sp_fs_get_kind_at(path), probe->kind); + fs_expect_bool(t, label, "exists", sp_fs_exists_at(path), probe->exists); + fs_expect_bool(t, label, "is_regular_file", sp_fs_is_file_at(path), probe->file); + fs_expect_bool(t, label, "is_dir", sp_fs_is_dir_at(path), probe->dir); + fs_expect_bool(t, label, "is_symlink", sp_fs_is_symlink_at(path), probe->symlink); + fs_expect_bool(t, label, "is_target_regular_file", sp_fs_is_target_file_at(path), probe->target_file); + fs_expect_bool(t, label, "is_target_dir", sp_fs_is_target_dir_at(path), probe->target_dir); + fs_expect_kind(t, label, sp_fs_get_kind_at(path), probe->kind); } - sp_sys_close(dir); return SP_OK; } diff --git a/test/fs/remove/os.c b/test/fs/remove/os.c index af247404..ed06a80e 100644 --- a/test/fs/remove/os.c +++ b/test/fs/remove/os.c @@ -263,12 +263,12 @@ sp_test_each(fs, remove, test_t, tests) { if (fs_setup_needs_symlinks(it->setup)) sp_test_skip_without_symlinks(); sp_mem_t mem = sp_test_arena(t); - sp_str_t sandbox = sp_test_dir(t); + sp_path_t sandbox = sp_path_resolve(sp_test_dir(t)); fs_apply_setup(t, sandbox, it->setup); - sp_str_t base = it->cwd ? sp_fs_join_path(mem, sandbox, sp_cstr_as_str(it->cwd)) : sandbox; + sp_path_t base = it->cwd ? sp_path_join(mem, sandbox, sp_cstr_as_str(it->cwd)) : sandbox; sp_sys_fd_t dir = SP_SYS_INVALID_FD; - sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), base, 0, &dir)); + sp_must_ok(t, sp_sys_open_dir_s(base.dir, base.sub, 0, &dir)); sp_path_t path = { .dir = dir, .sub = sp_cstr_as_str(it->path) }; sp_err_t result = it->dir ? sp_fs_remove_dir_at(path) : sp_fs_remove_file_at(path); @@ -283,10 +283,7 @@ sp_test_each(fs, remove, test_t, tests) { } sp_test(fs, remove_dir_deeper_than_path_max_is_name_too_long) { - sp_sys_fd_t dir = SP_SYS_INVALID_FD; - sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sp_test_dir(t), 0, &dir)); - - sp_path_t root = { .dir = dir, .sub = sp_str_lit("A") }; + sp_path_t root = sp_path_join(sp_test_arena(t), sp_path_resolve(sp_test_dir(t)), sp_str_lit("A")); sp_must_ok(t, sp_fs_create_dir_at(root)); sp_sys_fd_t cur = SP_SYS_INVALID_FD; @@ -318,27 +315,24 @@ sp_test(fs, remove_dir_deeper_than_path_max_is_name_too_long) { sp_expect_ok(t, sp_fs_remove_dir_at(root)); sp_expect(t, !sp_fs_exists_at(root)); - sp_sys_close(dir); return SP_OK; } #if defined(SP_POSIX) sp_test(fs, remove_dir_unwritable_subdir_fails) { - sp_sys_fd_t root = sp_sys_get_root(0); - sp_str_t locked = fs_path_c(t, "A/B"); - sp_must_ok(t, sp_fs_create_dir(locked)); - sp_must_ok(t, sp_fs_create_file(fs_path_c(t, "A/B/C"))); - sp_must_ok(t, sp_sys_set_file_perms_s(root, locked, (sp_sys_file_perms_t) { .value = 0555 })); + sp_mem_t mem = sp_test_arena(t); + sp_path_t sandbox = sp_path_resolve(sp_test_dir(t)); + sp_path_t locked = sp_path_join(mem, sandbox, sp_str_lit("A/B")); + sp_must_ok(t, sp_fs_create_dir_at(locked)); + sp_must_ok(t, sp_fs_create_file_at(sp_path_join(mem, sandbox, sp_str_lit("A/B/C")))); + sp_must_ok(t, sp_sys_set_file_perms_s(locked.dir, locked.sub, (sp_sys_file_perms_t) { .value = 0555 })); - sp_sys_fd_t dir = SP_SYS_INVALID_FD; - sp_must_ok(t, sp_sys_open_dir_s(root, sp_test_dir(t), 0, &dir)); - sp_err_t result = sp_fs_remove_dir_at((sp_path_t) { .dir = dir, .sub = sp_str_lit("A") }); - sp_sys_close(dir); + sp_err_t result = sp_fs_remove_dir_at(sp_path_join(mem, sandbox, sp_str_lit("A"))); if (!result) return sp_test_skip(t, "directory permissions not enforced"); - sp_must_ok(t, sp_sys_set_file_perms_s(root, locked, (sp_sys_file_perms_t) { .value = 0755 })); + sp_must_ok(t, sp_sys_set_file_perms_s(locked.dir, locked.sub, (sp_sys_file_perms_t) { .value = 0755 })); sp_expect_err_eq(t, result, SP_ERR_SYS_ACCESS_DENIED); - fs_expect_paths(t, sp_test_dir(t), (const fs_expected_path_t [FS_MAX_PATHS]) { + fs_expect_paths(t, sandbox, (const fs_expected_path_t [FS_MAX_PATHS]) { { .path = "A/B", .exists = true, .kind = SP_FS_KIND_DIR }, { .path = "A/B/C", .exists = true, .kind = SP_FS_KIND_FILE }, }); diff --git a/test/fs/write_atomic.c b/test/fs/write_atomic.c index bc763a80..f356c604 100644 --- a/test/fs/write_atomic.c +++ b/test/fs/write_atomic.c @@ -61,17 +61,11 @@ static const test_t tests [] = { }; sp_test_each(fs, write_atomic, test_t, tests) { - sp_str_t sandbox = sp_test_dir(t); + sp_path_t sandbox = sp_path_resolve(sp_test_dir(t)); fs_apply_setup(t, sandbox, it->setup); - sp_sys_fd_t dir = SP_SYS_INVALID_FD; - sp_must_ok(t, sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, 0, &dir)); - - sp_path_t path = { .dir = dir, .sub = sp_str_view(it->path) }; - sp_err_t err = sp_fs_write_atomic_at(path, sp_str_view(it->content)); - sp_sys_close(dir); - - sp_expect_err_eq(t, err, it->expect.err); + sp_path_t path = sp_path_join(sp_test_arena(t), sandbox, sp_str_view(it->path)); + sp_expect_err_eq(t, sp_fs_write_atomic_at(path, sp_str_view(it->content)), it->expect.err); fs_expect_paths(t, sandbox, it->expect.paths); fs_expect_no_temps(t, sandbox); return SP_OK; diff --git a/test/sys/fd_path.c b/test/sys/fd_path.c index 2d4b08b4..b3f6d03b 100644 --- a/test/sys/fd_path.c +++ b/test/sys/fd_path.c @@ -125,14 +125,13 @@ static bool same_file(sp_path_t a, sp_path_t b) { static sp_err_t run(sp_test_t* t, test_t* c) { sp_test_skip_on_wasm() - sp_sys_fd_t sandbox = SP_SYS_INVALID_FD; - sp_must_ok(t, sp_sys_open_dir_s(sp_fs_get_cwd(), sp_test_dir(t), 0, &sandbox)); - - sp_path_t path = { .dir = sandbox, .sub = sp_cstr_as_str(c->path) }; - sp_path_t alias = { .dir = sandbox, .sub = sp_cstr_as_str(c->alias) }; - sp_path_t decoy = { .dir = sandbox, .sub = sp_cstr_as_str(c->decoy) }; - sp_path_t other = { .dir = sandbox, .sub = sp_str_lit("B") }; - sp_path_t expect = { .dir = sandbox, .sub = sp_cstr_as_str(c->expect.path) }; + sp_mem_t mem = sp_test_arena(t); + sp_path_t sandbox = sp_path_resolve(sp_test_dir(t)); + sp_path_t path = sp_path_join(mem, sandbox, sp_cstr_as_str(c->path)); + sp_path_t alias = sp_path_join(mem, sandbox, sp_cstr_as_str(c->alias)); + sp_path_t decoy = sp_path_join(mem, sandbox, sp_cstr_as_str(c->decoy)); + sp_path_t other = sp_path_join(mem, sandbox, sp_str_lit("B")); + sp_path_t expect = sp_path_join(mem, sandbox, sp_cstr_as_str(c->expect.path)); sp_sys_pipe_t pipe = sp_zero; sp_sys_fd_t fd = SP_SYS_INVALID_FD; @@ -150,7 +149,7 @@ static sp_err_t run(sp_test_t* t, test_t* c) { break; } case TARGET_CWD: { - expect.dir = sp_fs_get_cwd(); + expect = sp_path_at_cwd(sp_cstr_as_str(c->expect.path)); fd = expect.dir; break; } @@ -215,7 +214,6 @@ static sp_err_t run(sp_test_t* t, test_t* c) { break; } } - sp_sys_close(sandbox); return SP_OK; } @@ -236,17 +234,18 @@ static const room_t rooms [] = { sp_test_each(sys, fd_path_room, room_t, rooms) { sp_test_skip_on_wasm() - sp_sys_fd_t sandbox = SP_SYS_INVALID_FD; - sp_must_ok(t, sp_sys_open_dir_s(sp_fs_get_cwd(), sp_test_dir(t), 0, &sandbox)); + sp_path_t sandbox = sp_path_resolve(sp_test_dir(t)); + sp_sys_fd_t dir = SP_SYS_INVALID_FD; + sp_must_ok(t, sp_sys_open_dir_s(sandbox.dir, sandbox.sub, 0, &dir)); c8 full [SP_PATH_MAX]; u64 len = 0; - sp_must_ok(t, sp_sys_get_fd_path(sandbox, full, sizeof(full), &len)); + sp_must_ok(t, sp_sys_get_fd_path(dir, full, sizeof(full), &len)); c8 buf [SP_PATH_MAX]; sp_for(at, sizeof(buf)) buf[at] = (c8)0xAB; u64 n = 1; - sp_err_t err = sp_sys_get_fd_path(sandbox, buf, (u64)((s64)len + it->room), &n); + sp_err_t err = sp_sys_get_fd_path(dir, buf, (u64)((s64)len + it->room), &n); sp_expect_err_eq(t, err, it->err); if (err) { @@ -258,6 +257,6 @@ sp_test_each(sys, fd_path_room, room_t, rooms) { sp_expect_mem_eq(t, buf, full, len); } - sp_sys_close(sandbox); + sp_sys_close(dir); return SP_OK; } diff --git a/test/sys/path.c b/test/sys/path.c index 6734c021..cf60788e 100644 --- a/test/sys/path.c +++ b/test/sys/path.c @@ -380,19 +380,22 @@ static sp_err_t run(sp_test_t* t, test_t* c) { } if (c->link) sp_test_skip_without_symlinks(); +#if defined(SP_WASM) + if (c->path_kind == PATH_ABS) return sp_test_skip(t, "no absolute names on wasm"); +#endif sp_mem_t mem = sp_test_arena(t); - sp_str_t sandbox = sp_test_dir(t); - if (c->file) sp_must_ok(t, sp_fs_create_file_str(sp_fs_join_path(mem, sandbox, sp_cstr_as_str(c->file)), sp_str_lit("A"))); - if (c->dir) sp_must_ok(t, sp_fs_create_dir(sp_fs_join_path(mem, sandbox, sp_cstr_as_str(c->dir)))); - if (c->link) sp_must_ok(t, sp_fs_create_sym_link(sp_cstr_as_str(c->target), sp_fs_join_path(mem, sandbox, sp_cstr_as_str(c->link)), c->dir_link ? SP_FS_KIND_DIR : SP_FS_KIND_FILE)); + sp_path_t sandbox = sp_path_resolve(sp_test_dir(t)); + if (c->file) sp_must_ok(t, sp_fs_create_file_str_at(sp_path_join(mem, sandbox, sp_cstr_as_str(c->file)), sp_str_lit("A"))); + if (c->dir) sp_must_ok(t, sp_fs_create_dir_at(sp_path_join(mem, sandbox, sp_cstr_as_str(c->dir)))); + if (c->link) sp_must_ok(t, sp_fs_create_sym_link_at(sp_cstr_as_str(c->target), sp_path_join(mem, sandbox, sp_cstr_as_str(c->link)), c->dir_link ? SP_FS_KIND_DIR : SP_FS_KIND_FILE)); sp_sys_fd_t sandbox_fd = SP_SYS_INVALID_FD; - sp_try(sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, 0, &sandbox_fd)); + sp_try(sp_sys_open_dir_s(sandbox.dir, sandbox.sub, 0, &sandbox_fd)); sp_str_t path = sp_cstr_as_str(c->path); if (c->path_kind == PATH_LONG) path = long_path(mem, c->path); - if (c->path_kind == PATH_ABS) path = abs_path(mem, sandbox, c->path); + if (c->path_kind == PATH_ABS) path = abs_path(mem, sp_fs_canonicalize_path_at(mem, sandbox), c->path); sp_sys_file_meta_t meta = sp_zero; sp_err_t err = SP_OK; diff --git a/test/sys/perms.c b/test/sys/perms.c index 5ab29fd7..18b26a1c 100644 --- a/test/sys/perms.c +++ b/test/sys/perms.c @@ -36,12 +36,11 @@ static const test_t tests [] = { static sp_err_t run(sp_test_t* t, test_t* it) { sp_test_skip_on_wasm(); - sp_str_t path = sp_fs_join_path(sp_test_arena(t), sp_test_dir(t), sp_str_lit("A")); - sp_fs_create_file(path); - sp_sys_fd_t root = sp_sys_get_root(0); + sp_path_t path = sp_path_join(sp_test_arena(t), sp_path_resolve(sp_test_dir(t)), sp_str_lit("A")); + sp_fs_create_file_at(path); sp_sys_file_meta_t meta = sp_zero; - sp_err_t err = sp_sys_get_path_metadata_s(root, path, &meta); + sp_err_t err = sp_sys_get_path_metadata_s(path.dir, path.sub, &meta); if (err == SP_ERR_SYS_UNSUPPORTED) return sp_test_skip(t, "metadata not supported"); sp_try(err); @@ -53,10 +52,10 @@ static sp_err_t run(sp_test_t* t, test_t* it) { case OP_EXECUTABLE: sp_sys_set_executable(&meta.perms, op.value); break; } } - sp_must_ok(t, sp_sys_set_file_perms_s(root, path, meta.perms)); + sp_must_ok(t, sp_sys_set_file_perms_s(path.dir, path.sub, meta.perms)); sp_sys_file_meta_t after = sp_zero; - sp_must_ok(t, sp_sys_get_path_metadata_s(root, path, &after)); + sp_must_ok(t, sp_sys_get_path_metadata_s(path.dir, path.sub, &after)); sp_expect_eq(t, sp_sys_is_read_only(after.perms), it->expect.read_only); #if !defined(SP_WIN32) sp_expect_eq(t, (after.perms.value & 0111) != 0, it->expect.executable); @@ -67,23 +66,22 @@ static sp_err_t run(sp_test_t* t, test_t* it) { sp_test_each_fn(sys, perms, test_t, tests, run); sp_test(sys, default_perms_then_read_only) { - sp_str_t path = sp_fs_join_path(sp_test_arena(t), sp_test_dir(t), sp_str_lit("A")); - sp_fs_create_file(path); - sp_sys_fd_t root = sp_sys_get_root(0); + sp_path_t path = sp_path_join(sp_test_arena(t), sp_path_resolve(sp_test_dir(t)), sp_str_lit("A")); + sp_fs_create_file_at(path); - sp_err_t err = sp_sys_set_file_perms_s(root, path, sp_sys_default_file_perms); + sp_err_t err = sp_sys_set_file_perms_s(path.dir, path.sub, sp_sys_default_file_perms); if (err == SP_ERR_SYS_UNSUPPORTED) return sp_test_skip(t, "set_file_perms not supported"); sp_try(err); sp_sys_file_meta_t meta = sp_zero; - sp_must_ok(t, sp_sys_get_path_metadata_s(root, path, &meta)); + sp_must_ok(t, sp_sys_get_path_metadata_s(path.dir, path.sub, &meta)); sp_expect(t, !sp_sys_is_read_only(meta.perms)); sp_sys_set_read_only(&meta.perms, true); - sp_must_ok(t, sp_sys_set_file_perms_s(root, path, meta.perms)); + sp_must_ok(t, sp_sys_set_file_perms_s(path.dir, path.sub, meta.perms)); sp_sys_file_meta_t after = sp_zero; - sp_must_ok(t, sp_sys_get_path_metadata_s(root, path, &after)); + sp_must_ok(t, sp_sys_get_path_metadata_s(path.dir, path.sub, &after)); sp_expect(t, sp_sys_is_read_only(after.perms)); return SP_OK; } @@ -91,17 +89,16 @@ sp_test(sys, default_perms_then_read_only) { sp_test(sys, mkdir_applies_perms) { sp_test_skip_on_win32(); sp_test_skip_on_wasm(); - sp_str_t path = sp_fs_join_path(sp_test_arena(t), sp_test_dir(t), sp_str_lit("D")); - sp_sys_fd_t root = sp_sys_get_root(0); + sp_path_t path = sp_path_join(sp_test_arena(t), sp_path_resolve(sp_test_dir(t)), sp_str_lit("D")); sp_sys_file_perms_t perms = sp_sys_default_dir_perms; sp_sys_set_read_only(&perms, true); - sp_err_t err = sp_sys_mkdir_s(root, path, perms); + sp_err_t err = sp_sys_mkdir_s(path.dir, path.sub, perms); if (err == SP_ERR_SYS_UNSUPPORTED) return sp_test_skip(t, "mkdir not supported"); sp_try(err); sp_sys_file_meta_t meta = sp_zero; - sp_must_ok(t, sp_sys_get_path_metadata_s(root, path, &meta)); + sp_must_ok(t, sp_sys_get_path_metadata_s(path.dir, path.sub, &meta)); sp_expect(t, sp_sys_is_read_only(meta.perms)); return SP_OK; } diff --git a/test/sys/readlink.c b/test/sys/readlink.c index cadbb7d6..d8933fe1 100644 --- a/test/sys/readlink.c +++ b/test/sys/readlink.c @@ -33,8 +33,8 @@ static const test_t tests [] = { static sp_err_t run(sp_test_t* t, test_t* c) { sp_mem_t mem = sp_test_arena(t); - sp_str_t sandbox = sp_test_dir(t); - sp_str_t path = sp_fs_join_path(mem, sandbox, sp_str_lit("L")); + sp_path_t sandbox = sp_path_resolve(sp_test_dir(t)); + sp_path_t path = sp_path_join(mem, sandbox, sp_str_lit("L")); sp_str_t expected = c->expect.target ? sp_cstr_as_str(c->expect.target) : sp_zero_s(sp_str_t); switch (c->setup) { @@ -42,18 +42,19 @@ static sp_err_t run(sp_test_t* t, test_t* c) { break; } case SETUP_FILE: { - sp_must_ok(t, sp_fs_create_file(path)); + sp_must_ok(t, sp_fs_create_file_at(path)); break; } case SETUP_LINK: { sp_test_skip_without_symlinks(); - sp_must_ok(t, sp_fs_create_sym_link(sp_cstr_as_str(c->target), path, SP_FS_KIND_FILE)); + sp_must_ok(t, sp_fs_create_sym_link_at(sp_cstr_as_str(c->target), path, SP_FS_KIND_FILE)); break; } case SETUP_LINK_ABSOLUTE: { + sp_test_skip_on_wasm(); sp_test_skip_without_symlinks(); - expected = sp_fs_join_path(mem, sandbox, sp_cstr_as_str(c->target)); - sp_must_ok(t, sp_fs_create_sym_link(expected, path, SP_FS_KIND_FILE)); + expected = sp_fs_join_path(mem, sp_fs_canonicalize_path_at(mem, sandbox), sp_cstr_as_str(c->target)); + sp_must_ok(t, sp_fs_create_sym_link_at(expected, path, SP_FS_KIND_FILE)); break; } } @@ -66,7 +67,7 @@ static sp_err_t run(sp_test_t* t, test_t* c) { c8 buf [SP_PATH_MAX]; sp_str_t target = sp_zero; - sp_err_t err = sp_sys_readlink_s(sp_sys_get_root(0), path, buf, c->size ? c->size : sizeof(buf), &target); + sp_err_t err = sp_sys_readlink_s(path.dir, path.sub, buf, c->size ? c->size : sizeof(buf), &target); if (err == SP_ERR_SYS_UNSUPPORTED) return sp_test_skip(t, "readlink not supported"); sp_expect_err_eq(t, err, c->expect.err); diff --git a/test/sys/sys.c b/test/sys/sys.c index 40483bf9..2cb178b2 100644 --- a/test/sys/sys.c +++ b/test/sys/sys.c @@ -36,36 +36,37 @@ sp_err_t sys_case_run(sp_test_t* t, sys_case_t* c) { if (sys_case_wants_symlinks(c)) sp_test_skip_without_symlinks(); sp_mem_t mem = sp_test_arena(t); - sp_str_t sandbox = sp_test_dir(t); - sp_try(sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, 0, &sandbox_fd)); + sp_path_t sandbox = sp_path_resolve(sp_test_dir(t)); sp_carr_for(c->setup, it) { sys_setup_t* ent = &c->setup[it]; if (!ent->path) break; - sp_str_t path = sp_fs_join_path(mem, sandbox, sp_cstr_as_str(ent->path)); + sp_path_t path = sp_path_join(mem, sandbox, sp_cstr_as_str(ent->path)); switch (ent->kind) { case SYS_SETUP_FILE: { - sp_fs_create_file_str(path, ent->content ? sp_cstr_as_str(ent->content) : sp_str_lit("")); + sp_fs_create_file_str_at(path, ent->content ? sp_cstr_as_str(ent->content) : sp_str_lit("")); break; } case SYS_SETUP_DIR: { - sp_fs_create_dir(path); + sp_fs_create_dir_at(path); break; } case SYS_SETUP_SYMLINK: case SYS_SETUP_DIR_SYMLINK: { sp_str_t target = sp_cstr_as_str(ent->target); sp_fs_kind_t kind = ent->kind == SYS_SETUP_DIR_SYMLINK ? SP_FS_KIND_DIR : SP_FS_KIND_FILE; - if (sp_fs_create_sym_link(target, path, kind)) { - sp_test_fail(t, "failed to create symlink {} -> {}", sp_fmt_str(path), sp_fmt_str(target)); - goto done; + if (sp_fs_create_sym_link_at(target, path, kind)) { + sp_test_fail(t, "failed to create symlink {} -> {}", sp_fmt_cstr(ent->path), sp_fmt_str(target)); + return SP_OK; } break; } } } + sp_try(sp_sys_open_dir_s(sandbox.dir, sandbox.sub, 0, &sandbox_fd)); + sp_carr_for(c->steps, it) { sys_step_t* step = &c->steps[it]; if (step->kind == SYS_STEP_NONE) break; @@ -148,20 +149,20 @@ sp_err_t sys_case_run(sp_test_t* t, sys_case_t* c) { sp_carr_for(c->expect, it) { sys_expect_t* ent = &c->expect[it]; if (!ent->path) break; - sp_str_t path = sp_fs_join_path(mem, sandbox, sp_cstr_as_str(ent->path)); + sp_path_t path = sp_path_join(mem, sandbox, sp_cstr_as_str(ent->path)); - bool exists = sp_fs_exists(path); + bool exists = sp_fs_exists_at(path); if (exists != ent->exists) { - sp_test_fail(t, "expected {} {} exist", sp_fmt_str(path), sp_fmt_cstr(ent->exists ? "to" : "not to")); + sp_test_fail(t, "expected {} {} exist", sp_fmt_cstr(ent->path), sp_fmt_cstr(ent->exists ? "to" : "not to")); continue; } if (ent->content) { sp_str_t actual = sp_zero; - sp_io_read_file(mem, path, &actual); + sp_io_read_file_at(mem, path, &actual); if (!sp_str_equal(actual, sp_cstr_as_str(ent->content))) { sp_test_record(t, (sp_test_failure_t) { - .message = sp_test_format(t, "content of {}", sp_fmt_str(path)), + .message = sp_test_format(t, "content of {}", sp_fmt_cstr(ent->path)), .expected = sp_test_format(t, "{.quote}", sp_fmt_cstr(ent->content)), .actual = sp_test_format(t, "{.quote}", sp_fmt_str(actual)), }); @@ -169,7 +170,6 @@ sp_err_t sys_case_run(sp_test_t* t, sys_case_t* c) { } } -done: - if (sandbox_fd != SP_SYS_INVALID_FD) sp_sys_close(sandbox_fd); + sp_sys_close(sandbox_fd); return SP_OK; } diff --git a/test/sys/timestamps.c b/test/sys/timestamps.c index 07d3fb3a..27e07293 100644 --- a/test/sys/timestamps.c +++ b/test/sys/timestamps.c @@ -31,13 +31,11 @@ static s64 field_ns(const sp_sys_file_meta_t* meta, field_t field) { } static sp_err_t run(sp_test_t* t, test_t* c) { - sp_mem_t mem = sp_test_arena(t); - sp_str_t path = sp_fs_join_path(mem, sp_test_dir(t), sp_str_lit("A")); - sp_fs_create_file_str(path, sp_str_lit("A")); + sp_path_t path = sp_path_join(sp_test_arena(t), sp_path_resolve(sp_test_dir(t)), sp_str_lit("A")); + sp_fs_create_file_str_at(path, sp_str_lit("A")); - sp_sys_fd_t root = sp_sys_get_root(0); sp_sys_file_meta_t before = sp_zero; - sp_err_t err = sp_sys_get_path_metadata_s(root, path, &before); + sp_err_t err = sp_sys_get_path_metadata_s(path.dir, path.sub, &before); if (err == SP_ERR_SYS_UNSUPPORTED) return sp_test_skip(t, "metadata not supported"); sp_try(err); @@ -46,7 +44,7 @@ static sp_err_t run(sp_test_t* t, test_t* c) { switch (c->op) { case OP_WRITE: { sp_sys_fd_t fd = SP_SYS_INVALID_FD; - sp_must_ok(t, sp_sys_open_s(root, path, SP_SYS_OPEN_MODE_WO, SP_SYS_OPEN_APPEND, &fd)); + sp_must_ok(t, sp_sys_open_s(path.dir, path.sub, SP_SYS_OPEN_MODE_WO, SP_SYS_OPEN_APPEND, &fd)); u64 n = 0; sp_expect_ok(t, sp_sys_write(fd, "B", 1, &n)); sp_sys_close(fd); @@ -56,13 +54,13 @@ static sp_err_t run(sp_test_t* t, test_t* c) { sp_test_skip_on_wasm(); sp_sys_file_perms_t perms = before.perms; sp_sys_set_read_only(&perms, true); - sp_must_ok(t, sp_sys_set_file_perms_s(root, path, perms)); + sp_must_ok(t, sp_sys_set_file_perms_s(path.dir, path.sub, perms)); break; } } sp_sys_file_meta_t after = sp_zero; - sp_try(sp_sys_get_path_metadata_s(root, path, &after)); + sp_try(sp_sys_get_path_metadata_s(path.dir, path.sub, &after)); bool moved = field_ns(&before, c->field) != field_ns(&after, c->field); if (moved != c->moves) { diff --git a/test/sys/transfer.c b/test/sys/transfer.c index 80bb0748..68d3e3c3 100644 --- a/test/sys/transfer.c +++ b/test/sys/transfer.c @@ -114,20 +114,19 @@ static sp_err_t run(sp_test_t* t, test_t* c) { #endif sp_mem_t mem = sp_test_arena(t); - sp_str_t sandbox = sp_test_dir(t); - sp_sys_fd_t sandbox_fd = SP_SYS_INVALID_FD; + sp_path_t sandbox = sp_path_resolve(sp_test_dir(t)); + sp_path_t src = sp_path_join(mem, sandbox, sp_str_lit("src")); + sp_path_t dst = sp_path_join(mem, sandbox, sp_str_lit("dst")); sp_sys_fd_t in = SP_SYS_INVALID_FD; sp_sys_fd_t out = SP_SYS_INVALID_FD; sp_sys_fd_t src_w = SP_SYS_INVALID_FD; sp_sys_fd_t dst_r = SP_SYS_INVALID_FD; u64 len = sp_cstr_len(c->data); - sp_try(sp_sys_open_dir_s(sp_sys_get_root(0), sandbox, 0, &sandbox_fd)); - switch (c->src) { case END_FILE: { - sp_fs_create_file_str(sp_fs_join_path(mem, sandbox, sp_str_lit("src")), sp_cstr_as_str(c->data)); - sp_must_ok(t, sp_sys_open_s(sandbox_fd, sp_str_lit("src"), SP_SYS_OPEN_MODE_RO, 0, &in)); + sp_must_ok(t, sp_fs_create_file_str_at(src, sp_cstr_as_str(c->data))); + sp_must_ok(t, sp_sys_open_s(src.dir, src.sub, SP_SYS_OPEN_MODE_RO, 0, &in)); break; } case END_PIPE: { @@ -146,10 +145,10 @@ static sp_err_t run(sp_test_t* t, test_t* c) { case END_FILE: { u32 flags = SP_SYS_OPEN_CREATE; if (c->prefill) { - sp_fs_create_file_str(sp_fs_join_path(mem, sandbox, sp_str_lit("dst")), sp_cstr_as_str(c->prefill)); + sp_must_ok(t, sp_fs_create_file_str_at(dst, sp_cstr_as_str(c->prefill))); flags = 0; } - sp_must_ok(t, sp_sys_open_s(sandbox_fd, sp_str_lit("dst"), SP_SYS_OPEN_MODE_WO, flags, &out)); + sp_must_ok(t, sp_sys_open_s(dst.dir, dst.sub, SP_SYS_OPEN_MODE_WO, flags, &out)); break; } case END_PIPE: { @@ -183,7 +182,7 @@ static sp_err_t run(sp_test_t* t, test_t* c) { sp_sys_close(out); out = SP_SYS_INVALID_FD; sp_str_t actual = sp_zero; - sp_io_read_file(mem, sp_fs_join_path(mem, sandbox, sp_str_lit("dst")), &actual); + sp_io_read_file_at(mem, dst, &actual); sp_expect_str_eq_c(t, actual, c->expect); break; } @@ -201,7 +200,6 @@ static sp_err_t run(sp_test_t* t, test_t* c) { if (out != SP_SYS_INVALID_FD) sp_sys_close(out); if (src_w != SP_SYS_INVALID_FD) sp_sys_close(src_w); if (dst_r != SP_SYS_INVALID_FD) sp_sys_close(dst_r); - if (sandbox_fd != SP_SYS_INVALID_FD) sp_sys_close(sandbox_fd); return SP_OK; } From cf3660bcd50aa619415d6d6e4427b0b1c700a752 Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Tue, 29 Sep 2026 03:01:29 -0400 Subject: [PATCH 52/72] move sp_test to rooted --- sp.h | 4 +- sp/sp_test.h | 97 +++++++++++++++++++++--------------------- test/fs/atomic.c | 4 +- test/fs/canonicalize.c | 2 +- test/fs/collect.c | 2 +- test/fs/copy.c | 12 +++--- test/fs/create_dir.c | 2 +- test/fs/create_file.c | 2 +- test/fs/dir/os.c | 2 +- test/fs/fd_relative.c | 7 ++- test/fs/get_cwd.c | 4 +- test/fs/it/enter.c | 2 +- test/fs/it/os.c | 2 +- test/fs/links.c | 4 +- test/fs/mod_time.c | 4 +- test/fs/predicates.c | 2 +- test/fs/remove/os.c | 6 +-- test/fs/resolve.c | 8 +++- test/fs/write_atomic.c | 2 +- test/sys/fd_path.c | 4 +- test/sys/path.c | 6 +-- test/sys/perms.c | 6 +-- test/sys/readlink.c | 4 +- test/sys/sys.c | 2 +- test/sys/timestamps.c | 2 +- test/sys/transfer.c | 2 +- test/test/dir.c | 18 ++++---- test/test/keep.c | 18 ++++---- test/test/main.c | 4 +- test/test/prune.c | 25 +++++------ 30 files changed, 133 insertions(+), 126 deletions(-) diff --git a/sp.h b/sp.h index b22bbbe5..3e9aca48 100644 --- a/sp.h +++ b/sp.h @@ -20277,9 +20277,9 @@ SP_PRIVATE s32 sp_fs_match_label(sp_str_t path, sp_str_t label) { } sp_path_t sp_path_resolve(sp_str_t path) { - sp_path_t best = sp_path_at_cwd(path); - if (!sp_fs_is_absolute(path)) return best; + if (!sp_fs_is_absolute(path)) return sp_path_at_cwd(path); + sp_path_t best = { .dir = SP_SYS_INVALID_FD, .sub = path }; s32 deepest = SP_STR_NO_MATCH; for (s32 it = 0;; it++) { sp_sys_fd_t root = sp_sys_get_root(it); diff --git a/sp/sp_test.h b/sp/sp_test.h index c6cafaaf..57f5eee1 100644 --- a/sp/sp_test.h +++ b/sp/sp_test.h @@ -369,6 +369,12 @@ typedef struct { #define sp_test_skip_on_linux() #endif +#if defined(SP_WASM) + #define sp_test_skip_without_absolute_names() return sp_test_skip(t, "no absolute names on wasm"); +#else + #define sp_test_skip_without_absolute_names() ((void)0) +#endif + #define sp_test_skip_without_symlinks() if (!sp_test_symlinks_supported(t)) return sp_test_skip(t, "symlinks not available"); SP_API sp_err_t sp_test_skip(sp_test_t* t, const c8* fmt, ...); @@ -388,7 +394,7 @@ SP_API void sp_test_note(sp_test_t* t, const c8* fmt, ...); SP_API sp_mem_t sp_test_mem(sp_test_t* t); SP_API sp_mem_t sp_test_arena(sp_test_t* t); -SP_API sp_str_t sp_test_dir(sp_test_t* t); +SP_API sp_path_t sp_test_dir(sp_test_t* t); SP_API const void* sp_test_user(sp_test_t* t); SP_API void sp_test_record(sp_test_t* t, sp_test_failure_t failure); @@ -724,7 +730,7 @@ struct sp_test_t { sp_mem_heap_t* tracking_heap; sp_mem_t tracked_mem; - sp_str_t dir; + sp_path_t dir; }; struct sp_test_runner_t { @@ -737,7 +743,7 @@ struct sp_test_runner_t { sp_da(const c8*) failed; sp_da(const c8*) skipped; sp_da(const c8*) updated; - sp_str_t dir_root; + sp_path_t dir_root; sp_test_once_t symlinks; sp_str_t golden_root; bool update; @@ -1018,11 +1024,11 @@ sp_mem_t sp_test_mem(sp_test_t* t) { return t->tracked_mem; } -sp_str_t sp_test_dir(sp_test_t* t) { - if (sp_str_empty(t->dir)) { +sp_path_t sp_test_dir(sp_test_t* t) { + if (sp_str_empty(t->dir.sub)) { sp_str_t leaf = sp_str_replace_c8(t->mem, sp_cstr_as_str(t->instance->name), '/', '_'); - t->dir = sp_fs_join_path(t->mem, t->runner->dir_root, leaf); - sp_err_t err = sp_fs_create_dir(t->dir); + t->dir = sp_path_join(t->mem, t->runner->dir_root, leaf); + sp_err_t err = sp_fs_create_dir_at(t->dir); if (err) sp_test_fail(t, "failed to create test dir: {}", sp_fmt_str(sp_test_err_str(t, err))); } return t->dir; @@ -1044,10 +1050,10 @@ sp_err_t sp_test_once(sp_test_once_t* once, sp_test_once_fn_t fn, void* user) { static sp_err_t sp_test_probe_symlinks(void* user) { sp_test_runner_t* runner = (sp_test_runner_t*)user; sp_mem_arena_marker_t s = sp_mem_begin_scratch(); - sp_str_t link = sp_fs_join_path(s.mem, runner->dir_root, sp_str_lit("L")); - sp_err_t err = sp_fs_create_dir(runner->dir_root); - if (!err) err = sp_fs_create_sym_link(sp_str_lit("A"), link, SP_FS_KIND_FILE); - if (!err) sp_fs_remove_file(link); + sp_path_t link = sp_path_join(s.mem, runner->dir_root, sp_str_lit("L")); + sp_err_t err = sp_fs_create_dir_at(runner->dir_root); + if (!err) err = sp_fs_create_sym_link_at(sp_str_lit("A"), link, SP_FS_KIND_FILE); + if (!err) sp_fs_remove_file_at(link); sp_mem_end_scratch(s); return err; } @@ -1420,8 +1426,8 @@ static sp_test_status_t sp_test_status(sp_test_t* t) { return SP_TEST_STATUS_OK; } -static sp_err_t sp_test_remove_dir(sp_str_t path) { - sp_err_t err = sp_fs_remove_dir(path); +static sp_err_t sp_test_remove_dir(sp_path_t path) { + sp_err_t err = sp_fs_remove_dir_at(path); return err == SP_ERR_SYS_NOT_FOUND ? SP_OK : err; } @@ -1464,8 +1470,8 @@ static void sp_test_report(sp_test_t* t, sp_tty_t* term, sp_test_status_t status sp_tty_fmt(term, " {.gray} {}\n", sp_fmt_cstr("note"), sp_fmt_str(t->notes[it])); } - if (!sp_str_empty(t->dir)) { - sp_tty_fmt(term, " {.gray} {}\n", sp_fmt_cstr("dir"), sp_fmt_str(t->dir)); + if (!sp_str_empty(t->dir.sub)) { + sp_tty_fmt(term, " {.gray} {}\n", sp_fmt_cstr("dir"), sp_fmt_str(t->dir.sub)); } sp_da_for(t->failures, it) { @@ -1600,14 +1606,14 @@ static void sp_test_run_instance(sp_test_runner_t* runner, const sp_test_instanc case SP_TEST_KEEP_ALWAYS: keep = true; break; } - if (!sp_str_empty(t->dir) && !keep) { + if (!sp_str_empty(t->dir.sub) && !keep) { sp_err_t err = sp_test_remove_dir(t->dir); if (err) { sp_test_fail(t, "failed to remove test dir: {}", sp_fmt_str(sp_test_err_str(t, err))); status = sp_test_status(t); } else { - t->dir = sp_zero_s(sp_str_t); + t->dir = sp_zero_s(sp_path_t); } } @@ -1621,7 +1627,7 @@ static void sp_test_run_instance(sp_test_runner_t* runner, const sp_test_instanc sp_mutex_lock(&runner->mutex); sp_io_write_str(&runner->out.base, text, SP_NULLPTR); sp_io_flush(&runner->out.base); - if (!sp_str_empty(t->dir)) runner->kept = true; + if (!sp_str_empty(t->dir.sub)) runner->kept = true; switch (status) { case SP_TEST_STATUS_FAIL: sp_da_push(runner->failed, instance->name); break; case SP_TEST_STATUS_SKIP: sp_da_push(runner->skipped, instance->name); break; @@ -1912,15 +1918,6 @@ static sp_str_t sp_test_run_name(sp_mem_t mem, sp_tm_epoch_t time) { return sp_str_replace_c8(mem, sp_tm_epoch_to_iso8601(mem, time), ':', '-'); } -static s32 sp_test_run_sort_kernel(const void* a, const void* b) { - const sp_fs_entry_t* ea = (const sp_fs_entry_t*)a; - const sp_fs_entry_t* eb = (const sp_fs_entry_t*)b; - bool da = ea->kind == SP_FS_KIND_DIR; - bool db = eb->kind == SP_FS_KIND_DIR; - if (da != db) return da ? SP_QSORT_A_FIRST : SP_QSORT_B_FIRST; - return sp_str_compare_alphabetical(ea->name, eb->name); -} - s32 sp_test_main(s32 argc, const c8** argv, const sp_test_entry_t* entries) { const c8* filter = SP_NULLPTR; u32 jobs = 1; @@ -1978,7 +1975,7 @@ s32 sp_test_main(s32 argc, const c8** argv, const sp_test_entry_t* entries) { { .name = "dir", .kind = SP_CLI_OPT_CSTR, - .summary = "root for test directories (defaults to $cwd/.sp/test)", + .summary = "root for test directories (defaults to .sp/test)", .placeholder = "dir", .ptr = &dir.opt, }, @@ -2117,35 +2114,39 @@ s32 sp_test_main(s32 argc, const c8** argv, const sp_test_entry_t* entries) { return 0; } - sp_str_t root = sp_str_lit(".sp/test"); - if (dir.env) root = sp_cstr_as_str(dir.env); - if (dir.opt) root = sp_cstr_as_str(dir.opt); - root = sp_fs_normalize_path(runner->mem, root); - if (!sp_fs_is_absolute(root)) root = sp_fs_join_path(runner->mem, sp_fs_get_cwd_path(runner->mem), root); + sp_str_t dir_name = sp_str_lit(".sp/test"); + if (dir.env) dir_name = sp_cstr_as_str(dir.env); + if (dir.opt) dir_name = sp_cstr_as_str(dir.opt); + dir_name = sp_fs_normalize_path(runner->mem, dir_name); + + sp_path_t root = sp_path_resolve(dir_name); + if (root.dir == SP_SYS_INVALID_FD) { + sp_fmt_io(&runner->out.base, "no root serves {.quote}\n", sp_fmt_str(dir_name)); + sp_io_flush(&runner->out.base); + return 1; + } sp_tm_epoch_t now = sp_tm_now_epoch(); sp_tm_epoch_t grace = { .s = now.s - SP_TEST_RUNS_GRACE_S, .ns = now.ns }; - sp_str_t runs_dir = sp_fs_join_path(runner->mem, root, sp_fs_get_stem(sp_fs_get_exe_path(runner->mem))); - runner->dir_root = sp_fs_join_path(runner->mem, runs_dir, sp_test_run_name(runner->mem, now)); + sp_path_t runs_dir = sp_path_join(runner->mem, root, sp_fs_get_stem(sp_fs_get_exe_path(runner->mem))); + runner->dir_root = sp_path_join(runner->mem, runs_dir, sp_test_run_name(runner->mem, now)); sp_str_t cutoff = sp_test_run_name(runner->mem, grace); - sp_da(sp_fs_entry_t) listing = SP_NULLPTR; - sp_fs_collect(runner->mem, runs_dir, &listing); - sp_da_sort(listing, sp_test_run_sort_kernel); - - u64 runs = 0; - sp_da_for(listing, it) { - if (listing[it].kind != SP_FS_KIND_DIR) break; - runs++; + sp_da(sp_str_t) runs = sp_da_new(runner->mem, sp_str_t); + sp_fs_for(runner->mem, runs_dir, it) { + if (it.entry.kind != SP_FS_KIND_DIR) continue; + sp_da_push(runs, sp_str_copy(runner->mem, it.entry.name)); } + sp_da_sort(runs, sp_str_sort_kernel_alphabetical); - u64 candidates = runs > SP_TEST_RUNS_RETAINED ? runs - SP_TEST_RUNS_RETAINED : 0; + u64 candidates = sp_da_size(runs) > SP_TEST_RUNS_RETAINED ? sp_da_size(runs) - SP_TEST_RUNS_RETAINED : 0; sp_for(it, candidates) { - if (sp_str_compare_alphabetical(listing[it].name, cutoff) != SP_QSORT_A_FIRST) break; - sp_err_t err = sp_test_remove_dir(listing[it].path); + if (sp_str_compare_alphabetical(runs[it], cutoff) != SP_QSORT_A_FIRST) break; + sp_path_t run = sp_path_join(runner->mem, runs_dir, runs[it]); + sp_err_t err = sp_test_remove_dir(run); if (err) { sp_tty_fmt(&runner->term, "> failed to prune {}: {}\n", - sp_fmt_str(listing[it].path), + sp_fmt_str(run.sub), sp_fmt_str(sp_test_err_name(runner->mem, err))); } } @@ -2211,7 +2212,7 @@ s32 sp_test_main(s32 argc, const c8** argv, const sp_test_entry_t* entries) { if (err) { sp_tty_fmt(&runner->term, "> {.red} {}: {}\n", sp_fmt_cstr("failed to remove"), - sp_fmt_str(runner->dir_root), + sp_fmt_str(runner->dir_root.sub), sp_fmt_str(sp_test_err_name(runner->mem, err))); sp_io_flush(&runner->out.base); } diff --git a/test/fs/atomic.c b/test/fs/atomic.c index e5e40504..9d75b918 100644 --- a/test/fs/atomic.c +++ b/test/fs/atomic.c @@ -239,7 +239,7 @@ static sp_err_t drive(sp_fs_atomic_t* af, sp_err_t err, const test_t* it) { } sp_test_each(fs, atomic_open_at, test_t, open_at_tests) { - sp_path_t sandbox = sp_path_resolve(sp_test_dir(t)); + sp_path_t sandbox = sp_test_dir(t); fs_apply_setup(t, sandbox, it->setup); sp_sys_fd_t dir = SP_SYS_INVALID_FD; @@ -256,7 +256,7 @@ sp_test_each(fs, atomic_open_at, test_t, open_at_tests) { } sp_test_each(fs, atomic_staged, test_t, staged_tests) { - sp_path_t sandbox = sp_path_resolve(sp_test_dir(t)); + sp_path_t sandbox = sp_test_dir(t); fs_apply_setup(t, sandbox, it->setup); sp_sys_fd_t dir = SP_SYS_INVALID_FD; diff --git a/test/fs/canonicalize.c b/test/fs/canonicalize.c index b4d5837b..0449a544 100644 --- a/test/fs/canonicalize.c +++ b/test/fs/canonicalize.c @@ -161,7 +161,7 @@ sp_test_each(fs, canonicalize, test_t, tests) { if (fs_setup_needs_symlinks(it->setup)) sp_test_skip_without_symlinks(); sp_mem_t mem = sp_test_arena(t); - sp_path_t sandbox = sp_path_resolve(sp_test_dir(t)); + sp_path_t sandbox = sp_test_dir(t); fs_apply_setup(t, sandbox, it->setup); sp_sys_fd_t dir = SP_SYS_INVALID_FD; diff --git a/test/fs/collect.c b/test/fs/collect.c index 97e194d3..12787f9d 100644 --- a/test/fs/collect.c +++ b/test/fs/collect.c @@ -55,7 +55,7 @@ static const test_t tests [] = { sp_test_each(fs, collect, test_t, tests) { sp_mem_t mem = sp_test_arena(t); - sp_path_t sandbox = sp_path_resolve(sp_test_dir(t)); + sp_path_t sandbox = sp_test_dir(t); fs_apply_setup(t, sandbox, it->setup); sp_str_t root = sp_path_join(mem, sandbox, sp_str_lit("R")).sub; diff --git a/test/fs/copy.c b/test/fs/copy.c index 0a7cbae3..4823a281 100644 --- a/test/fs/copy.c +++ b/test/fs/copy.c @@ -532,7 +532,7 @@ sp_test_each(fs, copy, test_t, tests) { if (fs_setup_needs_symlinks(it->setup)) sp_test_skip_without_symlinks(); sp_mem_t mem = sp_test_arena(t); - sp_path_t sandbox = sp_path_resolve(sp_test_dir(t)); + sp_path_t sandbox = sp_test_dir(t); fs_apply_setup(t, sandbox, it->setup); sp_sys_fd_t dir = SP_SYS_INVALID_FD; @@ -574,7 +574,7 @@ static const size_test_t sizes [] = { sp_test_each(fs, copy_size, size_test_t, sizes) { sp_mem_t mem = sp_test_arena(t); - sp_path_t sandbox = sp_path_resolve(sp_test_dir(t)); + sp_path_t sandbox = sp_test_dir(t); sp_path_t from = sp_path_join(mem, sandbox, sp_str_lit("A")); sp_path_t to = sp_path_join(mem, sandbox, sp_str_lit("B")); @@ -600,7 +600,7 @@ static sp_sys_file_perms_t mode(u32 value) { // postcondition: dest has source's bytes and mode, on a fresh inode; timestamps unspecified sp_test(fs, copy_file_preserves_mode, .serial = true) { sp_mem_t mem = sp_test_arena(t); - sp_path_t sandbox = sp_path_resolve(sp_test_dir(t)); + sp_path_t sandbox = sp_test_dir(t); sp_path_t from = sp_path_join(mem, sandbox, sp_str_lit("A")); sp_path_t to = sp_path_join(mem, sandbox, sp_str_lit("B")); sp_must_ok(t, sp_fs_create_file_str_at(from, sp_str_lit("A"))); @@ -628,7 +628,7 @@ sp_test(fs, copy_file_preserves_mode, .serial = true) { sp_test(fs, copy_file_replaces_read_only_dest) { sp_mem_t mem = sp_test_arena(t); - sp_path_t sandbox = sp_path_resolve(sp_test_dir(t)); + sp_path_t sandbox = sp_test_dir(t); sp_path_t from = sp_path_join(mem, sandbox, sp_str_lit("A")); sp_path_t to = sp_path_join(mem, sandbox, sp_str_lit("B")); sp_must_ok(t, sp_fs_create_file_str_at(from, sp_str_lit("A"))); @@ -645,7 +645,7 @@ sp_test(fs, copy_file_replaces_read_only_dest) { sp_test(fs, copy_file_unwritable_parent_leaves_nothing) { sp_mem_t mem = sp_test_arena(t); - sp_path_t sandbox = sp_path_resolve(sp_test_dir(t)); + sp_path_t sandbox = sp_test_dir(t); sp_path_t from = sp_path_join(mem, sandbox, sp_str_lit("A")); sp_path_t dir = sp_path_join(mem, sandbox, sp_str_lit("D")); sp_path_t to = sp_path_join(mem, sandbox, sp_str_lit("D/B")); @@ -665,7 +665,7 @@ sp_test(fs, copy_file_unwritable_parent_leaves_nothing) { sp_test(fs, copy_tree_unreadable_subdir_fails) { sp_mem_t mem = sp_test_arena(t); - sp_path_t sandbox = sp_path_resolve(sp_test_dir(t)); + sp_path_t sandbox = sp_test_dir(t); sp_path_t from = sp_path_join(mem, sandbox, sp_str_lit("A")); sp_path_t locked = sp_path_join(mem, sandbox, sp_str_lit("A/B")); sp_path_t to = sp_path_join(mem, sandbox, sp_str_lit("E")); diff --git a/test/fs/create_dir.c b/test/fs/create_dir.c index 26b3157c..accbba3b 100644 --- a/test/fs/create_dir.c +++ b/test/fs/create_dir.c @@ -133,7 +133,7 @@ static const test_t tests [] = { sp_test_each(fs, create_dir, test_t, tests) { if (fs_setup_needs_symlinks(it->setup)) sp_test_skip_without_symlinks(); - sp_path_t sandbox = sp_path_resolve(sp_test_dir(t)); + sp_path_t sandbox = sp_test_dir(t); fs_apply_setup(t, sandbox, it->setup); sp_path_t target = sp_path_join(sp_test_arena(t), sandbox, sp_cstr_as_str(it->target)); diff --git a/test/fs/create_file.c b/test/fs/create_file.c index 6f60ae25..4af364ae 100644 --- a/test/fs/create_file.c +++ b/test/fs/create_file.c @@ -71,7 +71,7 @@ static const test_t tests [] = { }; sp_test_each(fs, create_file, test_t, tests) { - sp_path_t sandbox = sp_path_resolve(sp_test_dir(t)); + sp_path_t sandbox = sp_test_dir(t); fs_apply_setup(t, sandbox, it->setup); sp_path_t path = sp_path_join(sp_test_arena(t), sandbox, sp_str_view(it->path)); diff --git a/test/fs/dir/os.c b/test/fs/dir/os.c index 2132c613..84c30c5d 100644 --- a/test/fs/dir/os.c +++ b/test/fs/dir/os.c @@ -91,7 +91,7 @@ sp_test_each(fs, dir, test_t, tests) { if (fs_setup_needs_symlinks(it->setup)) sp_test_skip_without_symlinks(); sp_mem_t mem = sp_test_arena(t); - sp_path_t sandbox = sp_path_resolve(sp_test_dir(t)); + sp_path_t sandbox = sp_test_dir(t); fs_apply_setup(t, sandbox, it->setup); sp_path_t dir = it->dir ? sp_path_join(mem, sandbox, sp_cstr_as_str(it->dir)) : sandbox; diff --git a/test/fs/fd_relative.c b/test/fs/fd_relative.c index 2dbc3106..fda8ee7f 100644 --- a/test/fs/fd_relative.c +++ b/test/fs/fd_relative.c @@ -110,11 +110,10 @@ sp_test_each(fs, fd_relative, test_t, tests) { #if !defined(SP_WIN32) if (it->win32) return sp_test_skip(t, "windows only"); #endif -#if defined(SP_WASM) - if (it->op == OP_OPEN_ABS) return sp_test_skip(t, "no absolute names on wasm"); -#endif + if (it->op == OP_OPEN_ABS) sp_test_skip_without_absolute_names(); + sp_mem_t mem = sp_test_arena(t); - sp_path_t sandbox = sp_path_resolve(sp_test_dir(t)); + sp_path_t sandbox = sp_test_dir(t); fs_apply_setup(t, sandbox, it->setup); sp_path_t cwd_path = sp_path_join(mem, sandbox, sp_str_view(it->cwd)); diff --git a/test/fs/get_cwd.c b/test/fs/get_cwd.c index ecdafeff..6b600b24 100644 --- a/test/fs/get_cwd.c +++ b/test/fs/get_cwd.c @@ -19,7 +19,7 @@ sp_test(fs, get_cwd_unlinked_cwd_has_no_path, .serial = true) { sp_str_t original = sp_fs_get_cwd_path(mem); sp_must_gt(t, original.len, 0); - sp_path_t sandbox = sp_path_resolve(sp_test_dir(t)); + sp_path_t sandbox = sp_test_dir(t); sp_sys_fd_t dir = SP_SYS_INVALID_FD; sp_must_ok(t, sp_sys_open_dir_s(sandbox.dir, sandbox.sub, 0, &dir)); @@ -49,7 +49,7 @@ sp_test(fs, get_cwd_is_root_zero) { sp_test(fs, path_at_cwd_passes_through) { sp_test_skip_on_wasm() - sp_path_t file = sp_path_join(sp_test_arena(t), sp_path_resolve(sp_test_dir(t)), sp_str_lit("A")); + sp_path_t file = sp_path_join(sp_test_arena(t), sp_test_dir(t), sp_str_lit("A")); sp_must_ok(t, sp_fs_create_file_at(file)); sp_path_t path = sp_path_at_cwd(file.sub); diff --git a/test/fs/it/enter.c b/test/fs/it/enter.c index df11f443..4bee6d62 100644 --- a/test/fs/it/enter.c +++ b/test/fs/it/enter.c @@ -112,7 +112,7 @@ sp_test_each(fs, it_enter, test_t, tests) { if (fs_setup_needs_symlinks(it->setup)) sp_test_skip_without_symlinks(); sp_mem_t mem = sp_test_arena(t); - sp_path_t sandbox = sp_path_resolve(sp_test_dir(t)); + sp_path_t sandbox = sp_test_dir(t); fs_apply_setup(t, sandbox, it->setup); sp_path_t root = sp_path_join(mem, sandbox, sp_str_lit("R")); diff --git a/test/fs/it/os.c b/test/fs/it/os.c index 2afdf7a6..b83ec0ee 100644 --- a/test/fs/it/os.c +++ b/test/fs/it/os.c @@ -164,7 +164,7 @@ sp_test_each(fs, it, test_t, tests) { if (fs_setup_needs_symlinks(it->setup)) sp_test_skip_without_symlinks(); sp_mem_t mem = sp_test_arena(t); - sp_path_t sandbox = sp_path_resolve(sp_test_dir(t)); + sp_path_t sandbox = sp_test_dir(t); fs_apply_setup(t, sandbox, it->setup); sp_sys_fd_t dir = SP_SYS_INVALID_FD; diff --git a/test/fs/links.c b/test/fs/links.c index 6f108f20..094189d8 100644 --- a/test/fs/links.c +++ b/test/fs/links.c @@ -126,7 +126,7 @@ static const sym_t sym_links [] = { sp_test_each(fs, hard_link, hard_t, hard_links) { sp_mem_t mem = sp_test_arena(t); - sp_path_t sandbox = sp_path_resolve(sp_test_dir(t)); + sp_path_t sandbox = sp_test_dir(t); fs_apply_setup(t, sandbox, it->setup); sp_path_t target = sp_path_join(mem, sandbox, sp_cstr_as_str(it->target)); @@ -149,7 +149,7 @@ sp_test_each(fs, hard_link, hard_t, hard_links) { sp_test_each(fs, sym_link, sym_t, sym_links) { sp_test_skip_without_symlinks(); - sp_path_t sandbox = sp_path_resolve(sp_test_dir(t)); + sp_path_t sandbox = sp_test_dir(t); fs_apply_setup(t, sandbox, it->setup); sp_path_t link = sp_path_join(sp_test_arena(t), sandbox, sp_cstr_as_str(it->link)); diff --git a/test/fs/mod_time.c b/test/fs/mod_time.c index 9d4bd99e..861d8d0d 100644 --- a/test/fs/mod_time.c +++ b/test/fs/mod_time.c @@ -2,7 +2,7 @@ sp_test(fs, mod_time_nonzero) { sp_mem_t mem = sp_test_arena(t); - sp_path_t sandbox = sp_path_resolve(sp_test_dir(t)); + sp_path_t sandbox = sp_test_dir(t); sp_path_t file = sp_path_join(mem, sandbox, sp_str_lit("A")); sp_path_t dir = sp_path_join(mem, sandbox, sp_str_lit("B")); sp_fs_create_file_at(file); @@ -14,7 +14,7 @@ sp_test(fs, mod_time_nonzero) { } sp_test(fs, mod_time_updates_after_write) { - sp_path_t file = sp_path_join(sp_test_arena(t), sp_path_resolve(sp_test_dir(t)), sp_str_lit("A")); + sp_path_t file = sp_path_join(sp_test_arena(t), sp_test_dir(t), sp_str_lit("A")); sp_fs_create_file_str_at(file, sp_str_lit("A")); sp_tm_epoch_t before = sp_fs_get_mod_time_at(file); diff --git a/test/fs/predicates.c b/test/fs/predicates.c index ac633d2d..88c3949c 100644 --- a/test/fs/predicates.c +++ b/test/fs/predicates.c @@ -92,7 +92,7 @@ sp_test_each(fs, predicates, test_t, tests) { if (fs_setup_needs_symlinks(it->setup)) sp_test_skip_without_symlinks(); sp_mem_t mem = sp_test_arena(t); - sp_path_t sandbox = sp_path_resolve(sp_test_dir(t)); + sp_path_t sandbox = sp_test_dir(t); fs_apply_setup(t, sandbox, it->setup); sp_carr_for(it->probes, i) { diff --git a/test/fs/remove/os.c b/test/fs/remove/os.c index ed06a80e..1f5ff0f3 100644 --- a/test/fs/remove/os.c +++ b/test/fs/remove/os.c @@ -263,7 +263,7 @@ sp_test_each(fs, remove, test_t, tests) { if (fs_setup_needs_symlinks(it->setup)) sp_test_skip_without_symlinks(); sp_mem_t mem = sp_test_arena(t); - sp_path_t sandbox = sp_path_resolve(sp_test_dir(t)); + sp_path_t sandbox = sp_test_dir(t); fs_apply_setup(t, sandbox, it->setup); sp_path_t base = it->cwd ? sp_path_join(mem, sandbox, sp_cstr_as_str(it->cwd)) : sandbox; @@ -283,7 +283,7 @@ sp_test_each(fs, remove, test_t, tests) { } sp_test(fs, remove_dir_deeper_than_path_max_is_name_too_long) { - sp_path_t root = sp_path_join(sp_test_arena(t), sp_path_resolve(sp_test_dir(t)), sp_str_lit("A")); + sp_path_t root = sp_path_join(sp_test_arena(t), sp_test_dir(t), sp_str_lit("A")); sp_must_ok(t, sp_fs_create_dir_at(root)); sp_sys_fd_t cur = SP_SYS_INVALID_FD; @@ -321,7 +321,7 @@ sp_test(fs, remove_dir_deeper_than_path_max_is_name_too_long) { #if defined(SP_POSIX) sp_test(fs, remove_dir_unwritable_subdir_fails) { sp_mem_t mem = sp_test_arena(t); - sp_path_t sandbox = sp_path_resolve(sp_test_dir(t)); + sp_path_t sandbox = sp_test_dir(t); sp_path_t locked = sp_path_join(mem, sandbox, sp_str_lit("A/B")); sp_must_ok(t, sp_fs_create_dir_at(locked)); sp_must_ok(t, sp_fs_create_file_at(sp_path_join(mem, sandbox, sp_str_lit("A/B/C")))); diff --git a/test/fs/resolve.c b/test/fs/resolve.c index c5e5ec5e..8996e2da 100644 --- a/test/fs/resolve.c +++ b/test/fs/resolve.c @@ -104,7 +104,13 @@ static const test_t tests [] = { .name = "label_matches_whole_components", .roots = { { .label = "/B" }, { .label = "/A" } }, .path = "/AB/C", - .expect = { .sub = "/AB/C" }, + .expect = { .dir = DIR_NONE, .sub = "/AB/C" }, + }, + { + .name = "absolute_without_serving_root", + .roots = { { .label = "/A" } }, + .path = "/B/C", + .expect = { .dir = DIR_NONE, .sub = "/B/C" }, }, { .name = "deepest_label_wins", diff --git a/test/fs/write_atomic.c b/test/fs/write_atomic.c index f356c604..56996622 100644 --- a/test/fs/write_atomic.c +++ b/test/fs/write_atomic.c @@ -61,7 +61,7 @@ static const test_t tests [] = { }; sp_test_each(fs, write_atomic, test_t, tests) { - sp_path_t sandbox = sp_path_resolve(sp_test_dir(t)); + sp_path_t sandbox = sp_test_dir(t); fs_apply_setup(t, sandbox, it->setup); sp_path_t path = sp_path_join(sp_test_arena(t), sandbox, sp_str_view(it->path)); diff --git a/test/sys/fd_path.c b/test/sys/fd_path.c index b3f6d03b..5bcdaff2 100644 --- a/test/sys/fd_path.c +++ b/test/sys/fd_path.c @@ -126,7 +126,7 @@ static sp_err_t run(sp_test_t* t, test_t* c) { sp_test_skip_on_wasm() sp_mem_t mem = sp_test_arena(t); - sp_path_t sandbox = sp_path_resolve(sp_test_dir(t)); + sp_path_t sandbox = sp_test_dir(t); sp_path_t path = sp_path_join(mem, sandbox, sp_cstr_as_str(c->path)); sp_path_t alias = sp_path_join(mem, sandbox, sp_cstr_as_str(c->alias)); sp_path_t decoy = sp_path_join(mem, sandbox, sp_cstr_as_str(c->decoy)); @@ -234,7 +234,7 @@ static const room_t rooms [] = { sp_test_each(sys, fd_path_room, room_t, rooms) { sp_test_skip_on_wasm() - sp_path_t sandbox = sp_path_resolve(sp_test_dir(t)); + sp_path_t sandbox = sp_test_dir(t); sp_sys_fd_t dir = SP_SYS_INVALID_FD; sp_must_ok(t, sp_sys_open_dir_s(sandbox.dir, sandbox.sub, 0, &dir)); diff --git a/test/sys/path.c b/test/sys/path.c index cf60788e..afde5401 100644 --- a/test/sys/path.c +++ b/test/sys/path.c @@ -380,12 +380,10 @@ static sp_err_t run(sp_test_t* t, test_t* c) { } if (c->link) sp_test_skip_without_symlinks(); -#if defined(SP_WASM) - if (c->path_kind == PATH_ABS) return sp_test_skip(t, "no absolute names on wasm"); -#endif + if (c->path_kind == PATH_ABS) sp_test_skip_without_absolute_names(); sp_mem_t mem = sp_test_arena(t); - sp_path_t sandbox = sp_path_resolve(sp_test_dir(t)); + sp_path_t sandbox = sp_test_dir(t); if (c->file) sp_must_ok(t, sp_fs_create_file_str_at(sp_path_join(mem, sandbox, sp_cstr_as_str(c->file)), sp_str_lit("A"))); if (c->dir) sp_must_ok(t, sp_fs_create_dir_at(sp_path_join(mem, sandbox, sp_cstr_as_str(c->dir)))); if (c->link) sp_must_ok(t, sp_fs_create_sym_link_at(sp_cstr_as_str(c->target), sp_path_join(mem, sandbox, sp_cstr_as_str(c->link)), c->dir_link ? SP_FS_KIND_DIR : SP_FS_KIND_FILE)); diff --git a/test/sys/perms.c b/test/sys/perms.c index 18b26a1c..2bc3ecb3 100644 --- a/test/sys/perms.c +++ b/test/sys/perms.c @@ -36,7 +36,7 @@ static const test_t tests [] = { static sp_err_t run(sp_test_t* t, test_t* it) { sp_test_skip_on_wasm(); - sp_path_t path = sp_path_join(sp_test_arena(t), sp_path_resolve(sp_test_dir(t)), sp_str_lit("A")); + sp_path_t path = sp_path_join(sp_test_arena(t), sp_test_dir(t), sp_str_lit("A")); sp_fs_create_file_at(path); sp_sys_file_meta_t meta = sp_zero; @@ -66,7 +66,7 @@ static sp_err_t run(sp_test_t* t, test_t* it) { sp_test_each_fn(sys, perms, test_t, tests, run); sp_test(sys, default_perms_then_read_only) { - sp_path_t path = sp_path_join(sp_test_arena(t), sp_path_resolve(sp_test_dir(t)), sp_str_lit("A")); + sp_path_t path = sp_path_join(sp_test_arena(t), sp_test_dir(t), sp_str_lit("A")); sp_fs_create_file_at(path); sp_err_t err = sp_sys_set_file_perms_s(path.dir, path.sub, sp_sys_default_file_perms); @@ -89,7 +89,7 @@ sp_test(sys, default_perms_then_read_only) { sp_test(sys, mkdir_applies_perms) { sp_test_skip_on_win32(); sp_test_skip_on_wasm(); - sp_path_t path = sp_path_join(sp_test_arena(t), sp_path_resolve(sp_test_dir(t)), sp_str_lit("D")); + sp_path_t path = sp_path_join(sp_test_arena(t), sp_test_dir(t), sp_str_lit("D")); sp_sys_file_perms_t perms = sp_sys_default_dir_perms; sp_sys_set_read_only(&perms, true); diff --git a/test/sys/readlink.c b/test/sys/readlink.c index d8933fe1..a966295a 100644 --- a/test/sys/readlink.c +++ b/test/sys/readlink.c @@ -33,7 +33,7 @@ static const test_t tests [] = { static sp_err_t run(sp_test_t* t, test_t* c) { sp_mem_t mem = sp_test_arena(t); - sp_path_t sandbox = sp_path_resolve(sp_test_dir(t)); + sp_path_t sandbox = sp_test_dir(t); sp_path_t path = sp_path_join(mem, sandbox, sp_str_lit("L")); sp_str_t expected = c->expect.target ? sp_cstr_as_str(c->expect.target) : sp_zero_s(sp_str_t); @@ -51,7 +51,7 @@ static sp_err_t run(sp_test_t* t, test_t* c) { break; } case SETUP_LINK_ABSOLUTE: { - sp_test_skip_on_wasm(); + sp_test_skip_without_absolute_names(); sp_test_skip_without_symlinks(); expected = sp_fs_join_path(mem, sp_fs_canonicalize_path_at(mem, sandbox), sp_cstr_as_str(c->target)); sp_must_ok(t, sp_fs_create_sym_link_at(expected, path, SP_FS_KIND_FILE)); diff --git a/test/sys/sys.c b/test/sys/sys.c index 2cb178b2..a26b2a6f 100644 --- a/test/sys/sys.c +++ b/test/sys/sys.c @@ -36,7 +36,7 @@ sp_err_t sys_case_run(sp_test_t* t, sys_case_t* c) { if (sys_case_wants_symlinks(c)) sp_test_skip_without_symlinks(); sp_mem_t mem = sp_test_arena(t); - sp_path_t sandbox = sp_path_resolve(sp_test_dir(t)); + sp_path_t sandbox = sp_test_dir(t); sp_carr_for(c->setup, it) { sys_setup_t* ent = &c->setup[it]; diff --git a/test/sys/timestamps.c b/test/sys/timestamps.c index 27e07293..955a03d8 100644 --- a/test/sys/timestamps.c +++ b/test/sys/timestamps.c @@ -31,7 +31,7 @@ static s64 field_ns(const sp_sys_file_meta_t* meta, field_t field) { } static sp_err_t run(sp_test_t* t, test_t* c) { - sp_path_t path = sp_path_join(sp_test_arena(t), sp_path_resolve(sp_test_dir(t)), sp_str_lit("A")); + sp_path_t path = sp_path_join(sp_test_arena(t), sp_test_dir(t), sp_str_lit("A")); sp_fs_create_file_str_at(path, sp_str_lit("A")); sp_sys_file_meta_t before = sp_zero; diff --git a/test/sys/transfer.c b/test/sys/transfer.c index 68d3e3c3..776ce9f8 100644 --- a/test/sys/transfer.c +++ b/test/sys/transfer.c @@ -114,7 +114,7 @@ static sp_err_t run(sp_test_t* t, test_t* c) { #endif sp_mem_t mem = sp_test_arena(t); - sp_path_t sandbox = sp_path_resolve(sp_test_dir(t)); + sp_path_t sandbox = sp_test_dir(t); sp_path_t src = sp_path_join(mem, sandbox, sp_str_lit("src")); sp_path_t dst = sp_path_join(mem, sandbox, sp_str_lit("dst")); sp_sys_fd_t in = SP_SYS_INVALID_FD; diff --git a/test/test/dir.c b/test/test/dir.c index abe6e192..86d9ed67 100644 --- a/test/test/dir.c +++ b/test/test/dir.c @@ -35,13 +35,13 @@ sp_test_each(runner, dir, dir_case_t, dir_cases) { sp_test_skip_on_freestanding(); sp_mem_t mem = sp_test_arena(t); - sp_str_t sandbox = sp_test_dir(t); + sp_path_t sandbox = sp_test_dir(t); sp_str_t exe = sp_fs_get_exe_path(mem); sp_ps_config_t config = { .command = exe, .args = { sp_str_lit("child"), sp_str_lit("--filter"), sp_str_lit("child.pass"), sp_str_lit("--keep"), sp_str_lit("always") }, - .cwd = sandbox, + .cwd = sandbox.sub, .env = { .extra = { { .key = sp_str_lit("SP_TEST_DIR"), .value = sp_cstr_as_str(it->env ? it->env : "") }, @@ -54,7 +54,7 @@ sp_test_each(runner, dir, dir_case_t, dir_cases) { }; if (it->flag) { sp_ps_config_add_arg(mem, &config, sp_str_lit("--dir")); - sp_ps_config_add_arg(mem, &config, sp_fs_join_path(mem, sandbox, sp_cstr_as_str(it->flag))); + sp_ps_config_add_arg(mem, &config, sp_cstr_as_str(it->flag)); } sp_ps_output_t out = sp_ps_run(mem, config); @@ -62,13 +62,13 @@ sp_test_each(runner, dir, dir_case_t, dir_cases) { sp_must_ok(t, out.error); sp_must_eq(t, out.status.exit_code, 0); - sp_str_t root = sp_fs_join_path(mem, sandbox, sp_cstr_as_str(it->expect)); - sp_da(sp_fs_entry_t) runs = SP_NULLPTR; - sp_fs_collect(mem, sp_fs_join_path(mem, root, sp_fs_get_stem(exe)), &runs); - sp_must_eq(t, sp_da_size(runs), (u64)1); + sp_str_t runs = sp_fs_join_path(mem, sp_cstr_as_str(it->expect), sp_fs_get_stem(exe)); + sp_da(sp_str_t) names = sp_da_new(mem, sp_str_t); + sp_fs_for(mem, sp_path_join(mem, sandbox, runs), walk) sp_da_push(names, sp_str_copy(mem, walk.entry.name)); + sp_must_eq(t, sp_da_size(names), (u64)1); - sp_str_t dir = sp_fs_join_path(mem, runs[0].path, sp_str_lit("child.pass")); - sp_expect(t, sp_fs_is_file(sp_fs_join_path(mem, dir, sp_str_lit("A")))); + sp_str_t dir = sp_fs_join_path(mem, sp_fs_join_path(mem, runs, names[0]), sp_str_lit("child.pass")); + sp_expect(t, sp_fs_is_file_at(sp_path_join(mem, sandbox, sp_fs_join_path(mem, dir, sp_str_lit("A"))))); sp_expect(t, sp_str_contains(out.out, sp_test_format(t, "\n dir {}\n", sp_fmt_str(dir)))); return SP_OK; } diff --git a/test/test/keep.c b/test/test/keep.c index 72d2b527..9c3e0655 100644 --- a/test/test/keep.c +++ b/test/test/keep.c @@ -115,12 +115,13 @@ sp_test_each(runner, keep, keep_case_t, keep_cases) { sp_test_skip_on_freestanding(); sp_mem_t mem = sp_test_arena(t); - sp_str_t sandbox = sp_test_dir(t); + sp_path_t sandbox = sp_test_dir(t); sp_str_t exe = sp_fs_get_exe_path(mem); sp_ps_config_t config = { .command = exe, - .args = { sp_str_lit("child"), sp_str_lit("--filter"), sp_cstr_as_str(it->test), sp_str_lit("--dir"), sandbox }, + .args = { sp_str_lit("child"), sp_str_lit("--filter"), sp_cstr_as_str(it->test), sp_str_lit("--dir"), sp_str_lit("T") }, + .cwd = sandbox.sub, .env = { .extra = { { .key = sp_str_lit("SP_TEST_KEEP"), .value = sp_cstr_as_str(it->env ? it->env : "") }, @@ -142,18 +143,19 @@ sp_test_each(runner, keep, keep_case_t, keep_cases) { sp_expect_eq(t, out.status.exit_code, it->expect.exit_code); sp_expect(t, sp_str_contains(out.out, sp_test_format(t, "\n{} {} ", sp_fmt_cstr(it->test), sp_fmt_cstr(it->expect.status)))); - sp_da(sp_fs_entry_t) runs = SP_NULLPTR; - sp_fs_collect(mem, sp_fs_join_path(mem, sandbox, sp_fs_get_stem(exe)), &runs); + sp_str_t runs = sp_fs_join_path(mem, sp_str_lit("T"), sp_fs_get_stem(exe)); + sp_da(sp_str_t) names = sp_da_new(mem, sp_str_t); + sp_fs_for(mem, sp_path_join(mem, sandbox, runs), walk) sp_da_push(names, sp_str_copy(mem, walk.entry.name)); if (!it->expect.kept) { - sp_expect_eq(t, sp_da_size(runs), (u64)0); + sp_expect_eq(t, sp_da_size(names), (u64)0); sp_expect(t, !sp_str_contains(out.out, sp_str_lit("\n dir "))); return SP_OK; } - sp_must_eq(t, sp_da_size(runs), (u64)1); - sp_str_t dir = sp_fs_join_path(mem, runs[0].path, sp_cstr_as_str(it->test)); - sp_expect(t, sp_fs_is_file(sp_fs_join_path(mem, dir, sp_str_lit("A")))); + sp_must_eq(t, sp_da_size(names), (u64)1); + sp_str_t dir = sp_fs_join_path(mem, sp_fs_join_path(mem, runs, names[0]), sp_cstr_as_str(it->test)); + sp_expect(t, sp_fs_is_file_at(sp_path_join(mem, sandbox, sp_fs_join_path(mem, dir, sp_str_lit("A"))))); sp_expect(t, sp_str_contains(out.out, sp_test_format(t, "\n dir {}\n", sp_fmt_str(dir)))); return SP_OK; } diff --git a/test/test/main.c b/test/test/main.c index 30b91b65..426cfae4 100644 --- a/test/test/main.c +++ b/test/test/main.c @@ -117,8 +117,8 @@ static const sp_test_entry_t child_entries [] = { }; static sp_err_t child_touch(sp_test_t* t) { - sp_str_t path = sp_fs_join_path(sp_test_arena(t), sp_test_dir(t), sp_str_lit("A")); - return sp_fs_create_file_cstr(path, "A"); + sp_path_t path = sp_path_join(sp_test_arena(t), sp_test_dir(t), sp_str_lit("A")); + return sp_fs_create_file_cstr_at(path, "A"); } static sp_err_t child_pass(sp_test_t* t) { diff --git a/test/test/prune.c b/test/test/prune.c index b2375b11..748d3cf7 100644 --- a/test/test/prune.c +++ b/test/test/prune.c @@ -69,28 +69,29 @@ sp_test_each(runner, prune, prune_case_t, prune_cases) { sp_test_skip_on_freestanding(); sp_mem_t mem = sp_test_arena(t); - sp_str_t sandbox = sp_test_dir(t); + sp_path_t sandbox = sp_test_dir(t); sp_str_t exe = sp_fs_get_exe_path(mem); - sp_str_t runs = sp_fs_join_path(mem, sandbox, sp_fs_get_stem(exe)); + sp_path_t runs = sp_path_join(mem, sandbox, sp_fs_join_path(mem, sp_str_lit("T"), sp_fs_get_stem(exe))); sp_tm_epoch_t now = sp_tm_now_epoch(); - sp_must_ok(t, sp_fs_create_dir(runs)); - sp_str_t paths [PRUNE_MAX] = sp_zero; + sp_must_ok(t, sp_fs_create_dir_at(runs)); + sp_path_t paths [PRUNE_MAX] = sp_zero; sp_carr_for(it->seeds, at) { const prune_seed_t* seed = &it->seeds[at]; if (!seed->id) break; - paths[at] = sp_fs_join_path(mem, runs, prune_run_name(mem, now, seed->age_s)); + paths[at] = sp_path_join(mem, runs, prune_run_name(mem, now, seed->age_s)); if (seed->file) { - sp_must_ok(t, sp_fs_create_file_cstr(paths[at], "A")); + sp_must_ok(t, sp_fs_create_file_cstr_at(paths[at], "A")); } else { - sp_must_ok(t, sp_fs_create_dir(paths[at])); + sp_must_ok(t, sp_fs_create_dir_at(paths[at])); } } sp_ps_output_t out = sp_ps_run(mem, (sp_ps_config_t) { .command = exe, - .args = { sp_str_lit("child"), sp_str_lit("--filter"), sp_str_lit("child.pass"), sp_str_lit("--dir"), sandbox }, + .args = { sp_str_lit("child"), sp_str_lit("--filter"), sp_str_lit("child.pass"), sp_str_lit("--dir"), sp_str_lit("T") }, + .cwd = sandbox.sub, .env = { .extra = { { .key = sp_str_lit("SP_TEST_KEEP"), .value = sp_str_lit("never") }, @@ -108,12 +109,12 @@ sp_test_each(runner, prune, prune_case_t, prune_cases) { sp_da(sp_str_t) survivors = sp_da_new(mem, sp_str_t); sp_carr_for(it->seeds, at) { if (!it->seeds[at].id) break; - if (sp_fs_exists(paths[at])) sp_da_push(survivors, sp_cstr_as_str(it->seeds[at].id)); + if (sp_fs_exists_at(paths[at])) sp_da_push(survivors, sp_cstr_as_str(it->seeds[at].id)); } sp_expect_strs_eq(t, survivors, sp_da_size(survivors), it->expect); - sp_da(sp_fs_entry_t) entries = SP_NULLPTR; - sp_fs_collect(mem, runs, &entries); - sp_expect_eq(t, sp_da_size(entries), sp_da_size(survivors)); + u64 entries = 0; + sp_fs_for(mem, runs, walk) entries++; + sp_expect_eq(t, entries, sp_da_size(survivors)); return SP_OK; } From a898dadc71683aed99963b1542c17dc7435d5a7f Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Tue, 29 Sep 2026 04:03:04 -0400 Subject: [PATCH 53/72] fix dir/it symbols --- example/fs/workspace.c | 18 +++----- sp.h | 98 ++++++++++++++++++++++++------------------ sp/sp_msvc.h | 16 +++---- sp/sp_test.h | 4 +- test/fs/atomic.c | 8 ++-- test/fs/canonicalize.c | 10 ++--- test/fs/copy.c | 8 ++-- test/fs/dir/os.c | 13 +++--- test/fs/dir/sim.c | 8 ++-- test/fs/fd_relative.c | 2 +- test/fs/fs.h | 10 ++--- test/fs/get_cwd.c | 4 +- test/fs/it/enter_sim.c | 2 +- test/fs/it/os.c | 4 +- test/fs/it/sim.c | 2 +- test/fs/path_join.c | 2 +- test/fs/path_parent.c | 33 ++++++++++++++ test/fs/remove/os.c | 16 +++---- test/fs/remove/sim.c | 2 +- test/io/file.c | 8 ++-- 20 files changed, 153 insertions(+), 115 deletions(-) create mode 100644 test/fs/path_parent.c diff --git a/example/fs/workspace.c b/example/fs/workspace.c index b3e9e8ba..e0d79ae9 100644 --- a/example/fs/workspace.c +++ b/example/fs/workspace.c @@ -1,13 +1,9 @@ #define SP_IMPLEMENTATION #include "sp.h" -static sp_path_t in(sp_sys_fd_t dir, const c8* sub) { - return (sp_path_t) { .dir = dir, .sub = sp_cstr_as_str(sub) }; -} - static sp_err_t list(sp_sys_fd_t dir) { sp_mem_arena_marker_t s = sp_mem_begin_scratch(); - sp_fs_it_t it = sp_fs_it_new_at(s.mem, in(dir, "."), 0); + sp_fs_it_t it = sp_fs_it_new_at(s.mem, sp_path_at(dir, sp_str_lit(".")), 0); while (sp_fs_it_walk(&it)) { sp_log("{}", sp_fmt_str(it.entry.rel)); } @@ -19,11 +15,11 @@ static sp_err_t list(sp_sys_fd_t dir) { } static sp_err_t build(sp_sys_fd_t dir) { - sp_try(sp_fs_create_dir_at(in(dir, "src/nested"))); - sp_try(sp_fs_create_file_cstr_at(in(dir, "src/A"), "A")); - sp_try(sp_fs_create_file_cstr_at(in(dir, "src/nested/B"), "B")); - sp_try(sp_fs_copy_at(in(dir, "src"), in(dir, "dst"), SP_FS_ATOMIC_REPLACE)); - sp_try(sp_fs_remove_dir_at(in(dir, "src"))); + sp_try(sp_fs_create_dir_at(sp_path_at(dir, sp_str_lit("src/nested")))); + sp_try(sp_fs_create_file_cstr_at(sp_path_at(dir, sp_str_lit("src/A")), "A")); + sp_try(sp_fs_create_file_cstr_at(sp_path_at(dir, sp_str_lit("src/nested/B")), "B")); + sp_try(sp_fs_copy_at(sp_path_at(dir, sp_str_lit("src")), sp_path_at(dir, sp_str_lit("dst")), SP_FS_ATOMIC_REPLACE)); + sp_try(sp_fs_remove_dir_at(sp_path_at(dir, sp_str_lit("src")))); return list(dir); } @@ -31,7 +27,7 @@ static sp_err_t work(sp_path_t path) { sp_try(sp_fs_create_dir_at(path)); sp_sys_fd_t dir = SP_SYS_INVALID_FD; - sp_err_t err = sp_sys_open_dir_s(path.dir, path.sub, 0, &dir); + sp_err_t err = sp_fs_open_dir_at(path, &dir); if (!err) { err = build(dir); sp_sys_close(dir); diff --git a/sp.h b/sp.h index 3e9aca48..d8048937 100644 --- a/sp.h +++ b/sp.h @@ -3120,22 +3120,19 @@ SP_API sp_err_t sp_fs_create_sym_link_at(sp_str_t target, sp_path_t SP_API sp_err_t sp_fs_copy_at(sp_path_t from, sp_path_t to, sp_fs_atomic_mode_t mode); SP_API sp_err_t sp_fs_copy_file_at(sp_path_t from, sp_path_t to, sp_fs_atomic_mode_t mode); SP_API sp_err_t sp_fs_copy_tree_at(sp_path_t from, sp_path_t to, sp_fs_atomic_mode_t mode); +SP_API sp_err_t sp_fs_open_dir_at(sp_path_t path, sp_sys_fd_t* out); SP_API sp_sys_fd_t sp_fs_get_cwd(); -SP_API sp_path_t sp_path_at_cwd(sp_str_t sub); -SP_API sp_path_t sp_path_resolve(sp_str_t path); -SP_API sp_path_t sp_path_join(sp_mem_t mem, sp_path_t path, sp_str_t sub); - -// literally sys+normalize, if normalize isnt needed this isn't either? -SP_API sp_str_t sp_fs_canonicalize_path(sp_mem_t mem, sp_str_t path); -SP_API sp_str_t sp_fs_canonicalize_path_at(sp_mem_t mem, sp_path_t path); - -// just put these in os SP_API sp_str_t sp_fs_get_cwd_path(sp_mem_t mem); - -// these currently forward to sys+normalize; remove from fs AND sys and just put in os SP_API sp_str_t sp_fs_get_exe_path(sp_mem_t mem); SP_API sp_str_t sp_fs_get_storage_path(sp_mem_t mem); SP_API sp_str_t sp_fs_get_config_path(sp_mem_t mem); +SP_API sp_str_t sp_fs_canonicalize_path(sp_mem_t mem, sp_str_t path); +SP_API sp_str_t sp_fs_canonicalize_path_at(sp_mem_t mem, sp_path_t path); +SP_API sp_path_t sp_path_at(sp_sys_fd_t dir, sp_str_t sub); +SP_API sp_path_t sp_path_at_cwd(sp_str_t sub); +SP_API sp_path_t sp_path_resolve(sp_str_t path); +SP_API sp_path_t sp_path_join(sp_mem_t mem, sp_path_t path, sp_str_t sub); +SP_API sp_path_t sp_path_parent(sp_mem_t mem, sp_path_t path); /* sp_fs_it_t @@ -3156,10 +3153,10 @@ typedef struct { sp_sys_dir_it_t dir; sp_mem_buffer_t buf; u64 cursor; -} sp_fs_dir_t; +} sp_fs_dir_it_t; typedef struct { - sp_fs_dir_t dir; + sp_fs_dir_it_t dir; SP_ALIGNED u8 buf [SP_FS_IT_BUF_SIZE]; u32 len; u32 name_len; @@ -3184,9 +3181,9 @@ typedef struct { #define sp_fs_for_recursive(mem, path, it) \ for (sp_fs_it_t it = sp_fs_it_new_at(mem, path, 0); sp_fs_it_walk(&it);) -SP_API sp_err_t sp_fs_dir_open(sp_fs_dir_t* it, sp_sys_fd_t fd, sp_str_t path, u32 flags, sp_mem_slice_t buf); -SP_API sp_err_t sp_fs_dir_next(sp_fs_dir_t* it, sp_fs_dir_entry_t* out); -SP_API sp_err_t sp_fs_dir_close(sp_fs_dir_t* it); +SP_API sp_err_t sp_fs_dir_it_open(sp_fs_dir_it_t* it, sp_path_t path, u32 flags, sp_mem_slice_t buf); +SP_API sp_err_t sp_fs_dir_it_next(sp_fs_dir_it_t* it, sp_fs_dir_entry_t* out); +SP_API sp_err_t sp_fs_dir_it_close(sp_fs_dir_it_t* it); SP_API sp_fs_it_t sp_fs_it_new(sp_mem_t mem, sp_str_t path); SP_API sp_fs_it_t sp_fs_it_new_at(sp_mem_t mem, sp_path_t path, u32 flags); SP_API bool sp_fs_it_next(sp_fs_it_t* it); @@ -20119,6 +20116,10 @@ SP_PRIVATE u32 sp_fs_root_len(sp_str_t path, sp_fs_path_kind_t kind) { SP_UNREACHABLE_RETURN(0); } +SP_PRIVATE bool sp_fs_is_dot(sp_str_t name) { + return sp_str_equal(name, sp_str_lit(".")) || sp_str_equal(name, sp_str_lit("..")); +} + SP_PRIVATE u32 sp_fs_stem_len(sp_str_t name) { u32 hidden = 0; while (hidden < name.len && name.data[hidden] == '.') hidden++; @@ -20260,8 +20261,12 @@ sp_sys_fd_t sp_fs_get_cwd() { return sp_sys_get_root(0); } +sp_path_t sp_path_at(sp_sys_fd_t dir, sp_str_t sub) { + return (sp_path_t) { .dir = dir, .sub = sub }; +} + sp_path_t sp_path_at_cwd(sp_str_t sub) { - return (sp_path_t) { .dir = sp_fs_get_cwd(), .sub = sub }; + return sp_path_at(sp_fs_get_cwd(), sub); } SP_PRIVATE s32 sp_fs_match_label(sp_str_t path, sp_str_t label) { @@ -20279,7 +20284,7 @@ SP_PRIVATE s32 sp_fs_match_label(sp_str_t path, sp_str_t label) { sp_path_t sp_path_resolve(sp_str_t path) { if (!sp_fs_is_absolute(path)) return sp_path_at_cwd(path); - sp_path_t best = { .dir = SP_SYS_INVALID_FD, .sub = path }; + sp_path_t best = sp_path_at(SP_SYS_INVALID_FD, path); s32 deepest = SP_STR_NO_MATCH; for (s32 it = 0;; it++) { sp_sys_fd_t root = sp_sys_get_root(it); @@ -20294,12 +20299,21 @@ sp_path_t sp_path_resolve(sp_str_t path) { sp_str_t rest = sp_str_suffix(path, (s32)path.len - matched); deepest = matched; - best = (sp_path_t) { .dir = root, .sub = sp_str_empty(rest) ? sp_str_lit(".") : rest }; + best = sp_path_at(root, sp_str_empty(rest) ? sp_str_lit(".") : rest); } } sp_path_t sp_path_join(sp_mem_t mem, sp_path_t path, sp_str_t sub) { - return (sp_path_t) { .dir = path.dir, .sub = sp_fs_join_path(mem, path.sub, sub) }; + return sp_path_at(path.dir, sp_fs_join_path(mem, path.sub, sub)); +} + +sp_path_t sp_path_parent(sp_mem_t mem, sp_path_t path) { + if (sp_str_empty(path.sub) || sp_fs_is_dot(sp_fs_get_name(path.sub))) return sp_path_join(mem, path, sp_str_lit("..")); + return sp_path_at(path.dir, sp_fs_parent_path(path.sub)); +} + +sp_err_t sp_fs_open_dir_at(sp_path_t path, sp_sys_fd_t* out) { + return sp_sys_open_dir_s(path.dir, path.sub, 0, out); } sp_fs_kind_t sp_fs_get_link_kind_at(sp_path_t path) { @@ -20482,7 +20496,7 @@ sp_err_t sp_io_read_file(sp_mem_t mem, sp_str_t path, sp_str_t* content) { return sp_io_read_file_at(mem, sp_path_at_cwd(path), content); } -SP_PRIVATE sp_err_t sp_fs_create_parent_at(sp_sys_fd_t dir, sp_str_t path); +SP_PRIVATE sp_err_t sp_fs_create_parent_at(sp_path_t path); SP_PRIVATE sp_err_t sp_fs_mkdir_at(sp_sys_fd_t dir, sp_str_t path) { sp_err_t err = sp_sys_mkdir_s(dir, path, sp_sys_default_dir_perms); @@ -20497,7 +20511,7 @@ sp_err_t sp_fs_create_dir_at(sp_path_t path) { sp_err_t err = sp_fs_mkdir_at(path.dir, path.sub); if (err != SP_ERR_SYS_NOT_FOUND) return err; - sp_try(sp_fs_create_parent_at(path.dir, path.sub)); + sp_try(sp_fs_create_parent_at(path)); return sp_fs_mkdir_at(path.dir, path.sub); } @@ -20567,14 +20581,14 @@ sp_err_t sp_fs_remove_file(sp_str_t path) { return sp_fs_remove_file_at(sp_path_at_cwd(path)); } -sp_err_t sp_fs_dir_open(sp_fs_dir_t* it, sp_sys_fd_t fd, sp_str_t path, u32 flags, sp_mem_slice_t buf) { - *it = sp_zero_s(sp_fs_dir_t); +sp_err_t sp_fs_dir_it_open(sp_fs_dir_it_t* it, sp_path_t path, u32 flags, sp_mem_slice_t buf) { + *it = sp_zero_s(sp_fs_dir_it_t); // @spader wtf? sp_assert(buf.len >= SP_SYS_DIR_MIN_BUF); sp_sys_fd_t dir_fd = SP_SYS_INVALID_FD; - sp_try(sp_sys_open_dir_s(fd, path, flags, &dir_fd)); + sp_try(sp_sys_open_dir_s(path.dir, path.sub, flags, &dir_fd)); sp_err_t err = sp_sys_dir_it_open(dir_fd, &it->dir); if (err) { @@ -20587,7 +20601,7 @@ sp_err_t sp_fs_dir_open(sp_fs_dir_t* it, sp_sys_fd_t fd, sp_str_t path, u32 flag return SP_OK; } -sp_err_t sp_fs_dir_next(sp_fs_dir_t* it, sp_fs_dir_entry_t* out) { +sp_err_t sp_fs_dir_it_next(sp_fs_dir_it_t* it, sp_fs_dir_entry_t* out) { *out = sp_zero_s(sp_fs_dir_entry_t); while (true) { @@ -20602,7 +20616,7 @@ sp_err_t sp_fs_dir_next(sp_fs_dir_t* it, sp_fs_dir_entry_t* out) { sp_try(sp_sys_dir_it_parse(&it->dir, &it->buf, &it->cursor, &entry)); sp_str_t name = sp_str(entry.name, entry.len); - if (sp_str_equal(name, sp_str_lit(".")) || sp_str_equal(name, sp_str_lit(".."))) continue; + if (sp_fs_is_dot(name)) continue; out->name = name; out->kind = entry.kind; @@ -20610,13 +20624,13 @@ sp_err_t sp_fs_dir_next(sp_fs_dir_t* it, sp_fs_dir_entry_t* out) { } } -sp_err_t sp_fs_dir_close(sp_fs_dir_t* it) { +sp_err_t sp_fs_dir_it_close(sp_fs_dir_it_t* it) { return sp_sys_dir_it_close(&it->dir); } SP_PRIVATE void sp_fs_it_unwind(sp_fs_it_t* it) { sp_da_for(it->stack, i) { - sp_fs_dir_close(&it->stack[i].dir); + sp_fs_dir_it_close(&it->stack[i].dir); } sp_da_clear(it->stack); } @@ -20638,7 +20652,7 @@ SP_PRIVATE sp_err_t sp_fs_it_join(sp_fs_it_t* it, u32 prefix, sp_str_t name, u32 SP_PRIVATE sp_err_t sp_fs_it_push(sp_fs_it_t* it, sp_path_t path, u32 len, u32 flags) { sp_fs_it_frame_t frame = { .len = len, .name_len = path.sub.len }; - sp_try(sp_fs_dir_open(&frame.dir, path.dir, path.sub, flags, sp_mem_slice(frame.buf, SP_FS_IT_BUF_SIZE))); + sp_try(sp_fs_dir_it_open(&frame.dir, path, flags, sp_mem_slice(frame.buf, SP_FS_IT_BUF_SIZE))); sp_da_push(it->stack, frame); return SP_OK; } @@ -20649,7 +20663,7 @@ SP_PRIVATE sp_err_t sp_fs_it_step(sp_fs_it_t* it) { u32 root = sp_fs_it_child(it, it->stack[0].len); sp_fs_dir_entry_t d = sp_zero; - sp_try(sp_fs_dir_next(&top->dir, &d)); + sp_try(sp_fs_dir_it_next(&top->dir, &d)); if (d.name.data) { u32 len = 0; @@ -20661,7 +20675,7 @@ SP_PRIVATE sp_err_t sp_fs_it_step(sp_fs_it_t* it) { .name = sp_str(it->path + len - d.name.len, d.name.len), .kind = d.kind, }; - it->at = (sp_path_t) { .dir = top->dir.dir.fd, .sub = it->entry.name }; + it->at = sp_path_at(top->dir.dir.fd, it->entry.name); // If the OS didn't give us a hint, specifically get it from metadata if (it->entry.kind == SP_FS_KIND_NONE) { @@ -20671,7 +20685,7 @@ SP_PRIVATE sp_err_t sp_fs_it_step(sp_fs_it_t* it) { } sp_da_pop(it->stack); - sp_try(sp_fs_dir_close(&top->dir)); + sp_try(sp_fs_dir_it_close(&top->dir)); if (sp_da_empty(it->stack)) return SP_OK; sp_fs_it_frame_t* parent = sp_da_back(it->stack); @@ -20682,7 +20696,7 @@ SP_PRIVATE sp_err_t sp_fs_it_step(sp_fs_it_t* it) { .name = sp_str(it->path + top->len - top->name_len, top->name_len), .kind = SP_FS_KIND_DIR, }; - it->at = (sp_path_t) { .dir = parent->dir.dir.fd, .sub = it->entry.name }; + it->at = sp_path_at(parent->dir.dir.fd, it->entry.name); return SP_OK; } @@ -20822,9 +20836,7 @@ sp_err_t sp_fs_remove_dir(sp_str_t path) { */ sp_err_t sp_fs_remove_dir_at(sp_path_t path) { sp_str_t name = sp_fs_get_name(path.sub); - if (sp_str_empty(name) || sp_str_equal(name, sp_str_lit(".")) || sp_str_equal(name, sp_str_lit(".."))) { - return SP_ERR_SYS_INVALID; - } + if (sp_str_empty(name) || sp_fs_is_dot(name)) return SP_ERR_SYS_INVALID; sp_sys_file_meta_t meta = sp_zero; sp_try(sp_sys_get_link_metadata_s(path.dir, path.sub, &meta)); @@ -20891,10 +20903,10 @@ SP_PRIVATE sp_err_t sp_fs_temp_name(sp_str_t dir, sp_str_t name, c8 buf [SP_PATH return SP_OK; } -SP_PRIVATE sp_err_t sp_fs_create_parent_at(sp_sys_fd_t dir, sp_str_t path) { - sp_str_t parent = sp_fs_parent_path(path); - if (sp_str_equal(parent, sp_fs_trim_path(path))) return SP_OK; - sp_err_t err = sp_fs_create_dir_at((sp_path_t) { .dir = dir, .sub = parent }); +SP_PRIVATE sp_err_t sp_fs_create_parent_at(sp_path_t path) { + sp_str_t parent = sp_fs_parent_path(path.sub); + if (sp_str_equal(parent, sp_fs_trim_path(path.sub))) return SP_OK; + sp_err_t err = sp_fs_create_dir_at(sp_path_at(path.dir, parent)); return err == SP_ERR_SYS_EXISTS ? SP_ERR_SYS_NOT_DIR : err; } @@ -21090,10 +21102,10 @@ sp_err_t sp_fs_copy_tree_at(sp_path_t from, sp_path_t to, sp_fs_atomic_mode_t mo sp_sys_fd_t fd = SP_SYS_INVALID_FD; sp_err_t err = it.err; if (!err) err = sp_fs_create_dir_at(to); - if (!err) err = sp_sys_open_dir_s(to.dir, to.sub, 0, &fd); + if (!err) err = sp_fs_open_dir_at(to, &fd); while (!err && sp_fs_it_walk(&it)) { - sp_path_t dst = { .dir = fd, .sub = it.entry.rel }; + sp_path_t dst = sp_path_at(fd, it.entry.rel); switch (it.entry.kind) { case SP_FS_KIND_DIR: err = sp_fs_create_dir_at(dst); break; case SP_FS_KIND_FILE: err = sp_fs_copy_file_at(it.at, dst, mode); break; diff --git a/sp/sp_msvc.h b/sp/sp_msvc.h index 8d30f493..a8cb6bf8 100644 --- a/sp/sp_msvc.h +++ b/sp/sp_msvc.h @@ -452,14 +452,14 @@ static sp_msvc_err_t sp_msvc_find_sdks(sp_msvc_t* msvc, sp_msvc_arch_t arch) { sp_str_t lib = sp_fmt_buf(buf, sizeof(buf), "{}/Lib", sp_fmt_str(sp_msvc_path_str(&root))).value; SP_ALIGNED u8 dir_buf [SP_SYS_DIR_MIN_BUF]; - sp_fs_dir_t dir = sp_zero; - if (sp_fs_dir_open(&dir, sp_fs_get_cwd(), lib, 0, sp_mem_slice(dir_buf, sizeof(dir_buf)))) { + sp_fs_dir_it_t dir = sp_zero; + if (sp_fs_dir_it_open(&dir, sp_path_at_cwd(lib), 0, sp_mem_slice(dir_buf, sizeof(dir_buf)))) { return SP_MSVC_ERR_SDK_NOT_FOUND; } while (true) { sp_fs_dir_entry_t entry = sp_zero; - if (sp_fs_dir_next(&dir, &entry)) break; + if (sp_fs_dir_it_next(&dir, &entry)) break; if (!entry.name.data) break; if (entry.kind != SP_FS_KIND_DIR) continue; if (!sp_str_starts_with(entry.name, sp_str_lit("10."))) continue; @@ -470,7 +470,7 @@ static sp_msvc_err_t sp_msvc_find_sdks(sp_msvc_t* msvc, sp_msvc_arch_t arch) { sp_msvc_add_sdk(msvc, &sdk); } - sp_fs_dir_close(&dir); + sp_fs_dir_it_close(&dir); if (!msvc->num_sdks) return SP_MSVC_ERR_SDK_NOT_FOUND; return SP_MSVC_OK; @@ -499,14 +499,14 @@ static sp_msvc_err_t sp_msvc_find_installations(sp_msvc_t* msvc, sp_msvc_arch_t if (instances.err) return SP_MSVC_ERR_VS_NOT_FOUND; SP_ALIGNED u8 dir_buf [SP_SYS_DIR_MIN_BUF]; - sp_fs_dir_t dir = sp_zero; - if (sp_fs_dir_open(&dir, sp_fs_get_cwd(), instances.value, 0, sp_mem_slice(dir_buf, sizeof(dir_buf)))) { + sp_fs_dir_it_t dir = sp_zero; + if (sp_fs_dir_it_open(&dir, sp_path_at_cwd(instances.value), 0, sp_mem_slice(dir_buf, sizeof(dir_buf)))) { return SP_MSVC_ERR_VS_NOT_FOUND; } while (true) { sp_fs_dir_entry_t entry = sp_zero; - if (sp_fs_dir_next(&dir, &entry)) break; + if (sp_fs_dir_it_next(&dir, &entry)) break; if (!entry.name.data) break; if (entry.kind != SP_FS_KIND_DIR) continue; @@ -538,7 +538,7 @@ static sp_msvc_err_t sp_msvc_find_installations(sp_msvc_t* msvc, sp_msvc_arch_t sp_msvc_add_vs(msvc, &vs); } - sp_fs_dir_close(&dir); + sp_fs_dir_it_close(&dir); if (!msvc->num_installations) return SP_MSVC_ERR_VS_NOT_FOUND; return SP_MSVC_OK; diff --git a/sp/sp_test.h b/sp/sp_test.h index 57f5eee1..12ad7bf4 100644 --- a/sp/sp_test.h +++ b/sp/sp_test.h @@ -2128,7 +2128,9 @@ s32 sp_test_main(s32 argc, const c8** argv, const sp_test_entry_t* entries) { sp_tm_epoch_t now = sp_tm_now_epoch(); sp_tm_epoch_t grace = { .s = now.s - SP_TEST_RUNS_GRACE_S, .ns = now.ns }; - sp_path_t runs_dir = sp_path_join(runner->mem, root, sp_fs_get_stem(sp_fs_get_exe_path(runner->mem))); + sp_str_t exe = sp_fs_get_exe_path(runner->mem); + if (sp_str_empty(exe)) exe = sp_cstr_as_str(argv[0]); + sp_path_t runs_dir = sp_path_join(runner->mem, root, sp_fs_get_stem(exe)); runner->dir_root = sp_path_join(runner->mem, runs_dir, sp_test_run_name(runner->mem, now)); sp_str_t cutoff = sp_test_run_name(runner->mem, grace); diff --git a/test/fs/atomic.c b/test/fs/atomic.c index 9d75b918..1f457cfe 100644 --- a/test/fs/atomic.c +++ b/test/fs/atomic.c @@ -243,10 +243,10 @@ sp_test_each(fs, atomic_open_at, test_t, open_at_tests) { fs_apply_setup(t, sandbox, it->setup); sp_sys_fd_t dir = SP_SYS_INVALID_FD; - sp_must_ok(t, sp_sys_open_dir_s(sandbox.dir, sandbox.sub, 0, &dir)); + sp_must_ok(t, sp_fs_open_dir_at(sandbox, &dir)); sp_fs_atomic_t af = sp_zero; - sp_err_t err = drive(&af, sp_fs_atomic_open_at(&af, (sp_path_t) { .dir = dir, .sub = sp_str_view(it->path) }), it); + sp_err_t err = drive(&af, sp_fs_atomic_open_at(&af, sp_path_at(dir, sp_str_view(it->path))), it); sp_sys_close(dir); sp_expect_err_eq(t, err, it->expect.err); @@ -260,9 +260,9 @@ sp_test_each(fs, atomic_staged, test_t, staged_tests) { fs_apply_setup(t, sandbox, it->setup); sp_sys_fd_t dir = SP_SYS_INVALID_FD; - sp_must_ok(t, sp_sys_open_dir_s(sandbox.dir, sandbox.sub, 0, &dir)); + sp_must_ok(t, sp_fs_open_dir_at(sandbox, &dir)); - sp_path_t path = { .dir = dir, .sub = sp_str_view(it->path) }; + sp_path_t path = sp_path_at(dir, sp_str_view(it->path)); sp_fs_atomic_t af = sp_zero; sp_err_t err = drive(&af, sp_fs_atomic_open_staged_at(&af, path, sp_str_lit("S")), it); diff --git a/test/fs/canonicalize.c b/test/fs/canonicalize.c index 0449a544..36ce5ef7 100644 --- a/test/fs/canonicalize.c +++ b/test/fs/canonicalize.c @@ -165,9 +165,9 @@ sp_test_each(fs, canonicalize, test_t, tests) { fs_apply_setup(t, sandbox, it->setup); sp_sys_fd_t dir = SP_SYS_INVALID_FD; - sp_must_ok(t, sp_sys_open_dir_s(sandbox.dir, sandbox.sub, 0, &dir)); + sp_must_ok(t, sp_fs_open_dir_at(sandbox, &dir)); - sp_path_t input = { .dir = dir, .sub = sp_str_view(it->input) }; + sp_path_t input = sp_path_at(dir, sp_str_view(it->input)); sp_str_t result = sp_fs_canonicalize_path_at(mem, input); if (it->expect.empty) { @@ -187,14 +187,14 @@ sp_test_each(fs, canonicalize, test_t, tests) { sp_expect_str_eq_c(t, sp_fs_get_name(result), it->expect.name); } if (it->expect.exists) { - sp_expect(t, sp_fs_exists_at((sp_path_t) { .dir = dir, .sub = result })); + sp_expect(t, sp_fs_exists_at(sp_path_at(dir, result))); } if (it->expect.idempotent) { - sp_path_t again = { .dir = dir, .sub = result }; + sp_path_t again = sp_path_at(dir, result); sp_expect_str_eq(t, sp_fs_canonicalize_path_at(mem, again), result); } if (it->expect.same_as) { - sp_path_t other = { .dir = dir, .sub = sp_str_view(it->expect.same_as) }; + sp_path_t other = sp_path_at(dir, sp_str_view(it->expect.same_as)); sp_expect_str_eq(t, result, sp_fs_canonicalize_path_at(mem, other)); } sp_sys_close(dir); diff --git a/test/fs/copy.c b/test/fs/copy.c index 4823a281..0da774f0 100644 --- a/test/fs/copy.c +++ b/test/fs/copy.c @@ -536,14 +536,14 @@ sp_test_each(fs, copy, test_t, tests) { fs_apply_setup(t, sandbox, it->setup); sp_sys_fd_t dir = SP_SYS_INVALID_FD; - sp_must_ok(t, sp_sys_open_dir_s(sandbox.dir, sandbox.sub, 0, &dir)); + sp_must_ok(t, sp_fs_open_dir_at(sandbox, &dir)); sp_path_t dst_base = it->dst_dir ? sp_path_join(mem, sandbox, sp_cstr_as_str(it->dst_dir)) : sandbox; sp_sys_fd_t dst_dir = SP_SYS_INVALID_FD; - sp_must_ok(t, sp_sys_open_dir_s(dst_base.dir, dst_base.sub, 0, &dst_dir)); + sp_must_ok(t, sp_fs_open_dir_at(dst_base, &dst_dir)); - sp_path_t src = { .dir = dir, .sub = sp_cstr_as_str(it->src) }; - sp_path_t dst = { .dir = dst_dir, .sub = sp_cstr_as_str(it->dst) }; + sp_path_t src = sp_path_at(dir, sp_cstr_as_str(it->src)); + sp_path_t dst = sp_path_at(dst_dir, sp_cstr_as_str(it->dst)); sp_err_t result = SP_OK; switch (it->op) { diff --git a/test/fs/dir/os.c b/test/fs/dir/os.c index 84c30c5d..dc97c11a 100644 --- a/test/fs/dir/os.c +++ b/test/fs/dir/os.c @@ -110,27 +110,26 @@ sp_test_each(fs, dir, test_t, tests) { sp_sys_fd_t sandbox_fd = SP_SYS_INVALID_FD; if (it->relative) { - sp_try(sp_sys_open_dir_s(sandbox.dir, sandbox.sub, 0, &sandbox_fd)); + sp_try(sp_fs_open_dir_at(sandbox, &sandbox_fd)); } SP_ALIGNED u8 buf [SP_SYS_DIR_MIN_BUF]; - sp_fs_dir_t iter = sp_zero; - sp_err_t open_err = it->relative - ? sp_fs_dir_open(&iter, sandbox_fd, sp_cstr_as_str(it->dir), 0, sp_mem_slice(buf, sizeof(buf))) - : sp_fs_dir_open(&iter, dir.dir, dir.sub, 0, sp_mem_slice(buf, sizeof(buf))); + sp_fs_dir_it_t iter = sp_zero; + sp_path_t opened = it->relative ? sp_path_at(sandbox_fd, sp_cstr_as_str(it->dir)) : dir; + sp_err_t open_err = sp_fs_dir_it_open(&iter, opened, 0, sp_mem_slice(buf, sizeof(buf))); sp_expect_ok(t, open_err); if (!open_err) { sp_err_t walk = SP_OK; while (true) { sp_fs_dir_entry_t entry = sp_zero; - walk = sp_fs_dir_next(&iter, &entry); + walk = sp_fs_dir_it_next(&iter, &entry); if (walk) break; if (!entry.name.data) break; fs_match(t, matches, n, entry.name, entry.kind); } sp_expect_ok(t, walk); - sp_expect_ok(t, sp_fs_dir_close(&iter)); + sp_expect_ok(t, sp_fs_dir_it_close(&iter)); } fs_match_finish(t, matches, n); diff --git a/test/fs/dir/sim.c b/test/fs/dir/sim.c index cda2c93c..6821f4f5 100644 --- a/test/fs/dir/sim.c +++ b/test/fs/dir/sim.c @@ -41,19 +41,19 @@ sp_test_each(fs, dir_sim, test_t, tests, .serial = true) { sim_begin(&s, it->dirs); SP_ALIGNED u8 buf [SP_SYS_DIR_MIN_BUF]; - sp_fs_dir_t dir = sp_zero; - sp_err_t open_err = sp_fs_dir_open(&dir, sp_sys_get_root(0), sp_str_lit("T"), 0, sp_mem_slice(buf, sizeof(buf))); + sp_fs_dir_it_t dir = sp_zero; + sp_err_t open_err = sp_fs_dir_it_open(&dir, sp_path_at(sp_sys_get_root(0), sp_str_lit("T")), 0, sp_mem_slice(buf, sizeof(buf))); sp_expect_err_eq(t, open_err, it->expect.open); if (!open_err) { sp_err_t walk = SP_OK; while (true) { sp_fs_dir_entry_t entry = sp_zero; - walk = sp_fs_dir_next(&dir, &entry); + walk = sp_fs_dir_it_next(&dir, &entry); if (walk || !entry.name.data) break; } sp_expect_err_eq(t, walk, it->expect.walk); - sp_expect_ok(t, sp_fs_dir_close(&dir)); + sp_expect_ok(t, sp_fs_dir_it_close(&dir)); } sim_end(&s); diff --git a/test/fs/fd_relative.c b/test/fs/fd_relative.c index fda8ee7f..539ccf3b 100644 --- a/test/fs/fd_relative.c +++ b/test/fs/fd_relative.c @@ -118,7 +118,7 @@ sp_test_each(fs, fd_relative, test_t, tests) { sp_path_t cwd_path = sp_path_join(mem, sandbox, sp_str_view(it->cwd)); sp_sys_fd_t cwd = SP_SYS_INVALID_FD; - sp_must_ok(t, sp_sys_open_dir_s(cwd_path.dir, cwd_path.sub, 0, &cwd)); + sp_must_ok(t, sp_fs_open_dir_at(cwd_path, &cwd)); switch (it->op) { case OP_STAT: { diff --git a/test/fs/fs.h b/test/fs/fs.h index b2ee13a2..7d879947 100644 --- a/test/fs/fs.h +++ b/test/fs/fs.h @@ -48,10 +48,6 @@ static bool fs_setup_needs_symlinks(const fs_setup_t setup [FS_MAX_SETUP]) { return false; } -static sp_path_t fs_parent(sp_path_t path) { - return (sp_path_t) { .dir = path.dir, .sub = sp_fs_parent_path(path.sub) }; -} - static void fs_expect_bool(sp_test_t* t, sp_str_t path, const c8* label, bool actual, bool expected) { if (actual == expected) return; @@ -108,7 +104,8 @@ static void fs_apply_setup(sp_test_t* t, sp_path_t sandbox, const fs_setup_t set if (!ent->path) break; sp_path_t path = sp_path_join(mem, sandbox, sp_cstr_as_str(ent->path)); - sp_fs_create_dir_at(fs_parent(path)); + sp_path_t parent = sp_path_parent(mem, path); + sp_fs_create_dir_at(parent); sp_err_t err = SP_OK; switch (ent->kind) { @@ -128,8 +125,7 @@ static void fs_apply_setup(sp_test_t* t, sp_path_t sandbox, const fs_setup_t set } case FS_SETUP_FIFO: { #if defined(SP_POSIX) - sp_str_t parent = sp_fs_canonicalize_path_at(mem, fs_parent(path)); - sp_str_t fifo = sp_fs_join_path(mem, parent, sp_fs_get_name(path.sub)); + sp_str_t fifo = sp_fs_join_path(mem, sp_fs_canonicalize_path_at(mem, parent), sp_fs_get_name(path.sub)); err = mkfifo(sp_cstr_from_str(mem, fifo), 0644) ? SP_ERR_SYS : SP_OK; #else err = SP_ERR_SYS_UNSUPPORTED; diff --git a/test/fs/get_cwd.c b/test/fs/get_cwd.c index 6b600b24..8529dbf9 100644 --- a/test/fs/get_cwd.c +++ b/test/fs/get_cwd.c @@ -21,9 +21,9 @@ sp_test(fs, get_cwd_unlinked_cwd_has_no_path, .serial = true) { sp_path_t sandbox = sp_test_dir(t); sp_sys_fd_t dir = SP_SYS_INVALID_FD; - sp_must_ok(t, sp_sys_open_dir_s(sandbox.dir, sandbox.sub, 0, &dir)); + sp_must_ok(t, sp_fs_open_dir_at(sandbox, &dir)); - sp_path_t doomed = { .dir = dir, .sub = sp_str_lit("A") }; + sp_path_t doomed = sp_path_at(dir, sp_str_lit("A")); sp_err_t err = sp_fs_create_dir_at(doomed); if (!err) err = sp_sys_chdir_s(sp_fs_canonicalize_path_at(mem, doomed)); if (!err) err = sp_fs_remove_dir_at(doomed); diff --git a/test/fs/it/enter_sim.c b/test/fs/it/enter_sim.c index def4db4b..f21df3a3 100644 --- a/test/fs/it/enter_sim.c +++ b/test/fs/it/enter_sim.c @@ -109,7 +109,7 @@ sp_test_each(fs, it_enter_sim, test_t, tests, .serial = true) { u32 leaves = 0; u32 produced = 0; - sp_fs_it_t walk = sp_fs_it_new_at(mem, (sp_path_t) { .dir = sp_sys_get_root(0), .sub = sp_str_lit("T") }, 0); + sp_fs_it_t walk = sp_fs_it_new_at(mem, sp_path_at(sp_sys_get_root(0), sp_str_lit("T")), 0); while (sp_fs_it_next(&walk)) { if (produced < MAX_YIELDS && it->expect.yields[produced].path) { const yield_t* want = &it->expect.yields[produced]; diff --git a/test/fs/it/os.c b/test/fs/it/os.c index b83ec0ee..8863b0ea 100644 --- a/test/fs/it/os.c +++ b/test/fs/it/os.c @@ -168,10 +168,10 @@ sp_test_each(fs, it, test_t, tests) { fs_apply_setup(t, sandbox, it->setup); sp_sys_fd_t dir = SP_SYS_INVALID_FD; - sp_must_ok(t, sp_sys_open_dir_s(sandbox.dir, sandbox.sub, 0, &dir)); + sp_must_ok(t, sp_fs_open_dir_at(sandbox, &dir)); sp_str_t base = sp_str_lit("R"); - sp_path_t root = { .dir = dir, .sub = sp_cstr_as_str(it->root ? it->root : "R") }; + sp_path_t root = sp_path_at(dir, sp_cstr_as_str(it->root ? it->root : "R")); fs_match_t matches [FS_MAX_PATHS] = sp_zero; u32 n = 0; diff --git a/test/fs/it/sim.c b/test/fs/it/sim.c index f9e97da7..185f8412 100644 --- a/test/fs/it/sim.c +++ b/test/fs/it/sim.c @@ -192,7 +192,7 @@ sp_test_each(fs, it_sim, test_t, tests, .serial = true) { sim_t s = sp_zero; sim_begin(&s, it->dirs); - sp_fs_it_t walk = sp_fs_it_new_at(mem, (sp_path_t) { .dir = sp_sys_get_root(0), .sub = root }, it->flags); + sp_fs_it_t walk = sp_fs_it_new_at(mem, sp_path_at(sp_sys_get_root(0), root), it->flags); sp_expect_err_eq(t, walk.err, it->expect.open); sp_expect(t, !walk.entry.path.data); diff --git a/test/fs/path_join.c b/test/fs/path_join.c index 74e0bd0d..9b868f76 100644 --- a/test/fs/path_join.c +++ b/test/fs/path_join.c @@ -21,7 +21,7 @@ static const test_t tests [] = { }; sp_test_each(fs, path_join, test_t, tests) { - sp_path_t path = { .dir = (sp_sys_fd_t)DIR_FD, .sub = sp_cstr_as_str(it->a) }; + sp_path_t path = sp_path_at((sp_sys_fd_t)DIR_FD, sp_cstr_as_str(it->a)); sp_path_t result = sp_path_join(sp_test_arena(t), path, sp_cstr_as_str(it->b)); sp_expect_eq(t, result.dir, (sp_sys_fd_t)DIR_FD); sp_expect_str_eq_c(t, result.sub, it->expect); diff --git a/test/fs/path_parent.c b/test/fs/path_parent.c new file mode 100644 index 00000000..4a0d98e3 --- /dev/null +++ b/test/fs/path_parent.c @@ -0,0 +1,33 @@ +#include "sp.h" +#include "sp/sp_test.h" + +#define DIR_FD 9000 + +typedef struct { + const c8* name; + const c8* sub; + const c8* expect; +} test_t; + +static const test_t tests [] = { + { .name = "nested", .sub = "A/B", .expect = "A" }, + { .name = "bare", .sub = "A", .expect = "." }, + { .name = "trailing_sep", .sub = "A/B/", .expect = "A" }, + { .name = "root_label", .sub = ".", .expect = "./.." }, + { .name = "trailing_dot", .sub = "./", .expect = "./.." }, + { .name = "empty", .sub = "", .expect = ".." }, + { .name = "dotdot", .sub = "..", .expect = "../.." }, + { .name = "nested_dotdot", .sub = "A/..", .expect = "A/../.." }, + { .name = "trailing_dotdot", .sub = "A/../", .expect = "A/../.." }, + { .name = "nested_dot", .sub = "A/.", .expect = "A/./.." }, + { .name = "root", .sub = "/", .expect = "/" }, + { .name = "under_root", .sub = "/A", .expect = "/" }, +}; + +sp_test_each(fs, path_parent, test_t, tests) { + sp_path_t path = sp_path_at((sp_sys_fd_t)DIR_FD, sp_cstr_as_str(it->sub)); + sp_path_t result = sp_path_parent(sp_test_arena(t), path); + sp_expect_eq(t, result.dir, (sp_sys_fd_t)DIR_FD); + sp_expect_str_eq_c(t, result.sub, it->expect); + return SP_OK; +} diff --git a/test/fs/remove/os.c b/test/fs/remove/os.c index 1f5ff0f3..9d2a170e 100644 --- a/test/fs/remove/os.c +++ b/test/fs/remove/os.c @@ -268,9 +268,9 @@ sp_test_each(fs, remove, test_t, tests) { sp_path_t base = it->cwd ? sp_path_join(mem, sandbox, sp_cstr_as_str(it->cwd)) : sandbox; sp_sys_fd_t dir = SP_SYS_INVALID_FD; - sp_must_ok(t, sp_sys_open_dir_s(base.dir, base.sub, 0, &dir)); + sp_must_ok(t, sp_fs_open_dir_at(base, &dir)); - sp_path_t path = { .dir = dir, .sub = sp_cstr_as_str(it->path) }; + sp_path_t path = sp_path_at(dir, sp_cstr_as_str(it->path)); sp_err_t result = it->dir ? sp_fs_remove_dir_at(path) : sp_fs_remove_file_at(path); sp_expect_err_eq(t, result, it->expect.err); @@ -287,30 +287,30 @@ sp_test(fs, remove_dir_deeper_than_path_max_is_name_too_long) { sp_must_ok(t, sp_fs_create_dir_at(root)); sp_sys_fd_t cur = SP_SYS_INVALID_FD; - sp_must_ok(t, sp_sys_open_dir_s(root.dir, root.sub, 0, &cur)); + sp_must_ok(t, sp_fs_open_dir_at(root, &cur)); sp_for(level, DEEP_LEVELS) { - sp_path_t child = { .dir = cur, .sub = sp_str_lit(DEEP_NAME) }; + sp_path_t child = sp_path_at(cur, sp_str_lit(DEEP_NAME)); sp_must_ok(t, sp_fs_create_dir_at(child)); sp_sys_fd_t next = SP_SYS_INVALID_FD; - sp_must_ok(t, sp_sys_open_dir_s(child.dir, child.sub, 0, &next)); + sp_must_ok(t, sp_fs_open_dir_at(child, &next)); sp_sys_close(cur); cur = next; } - sp_must_ok(t, sp_fs_create_file_at((sp_path_t) { .dir = cur, .sub = sp_str_lit("F") })); + sp_must_ok(t, sp_fs_create_file_at(sp_path_at(cur, sp_str_lit("F")))); sp_sys_close(cur); sp_expect_err_eq(t, sp_fs_remove_dir_at(root), SP_ERR_SYS_NAME_TOO_LONG); sp_expect(t, sp_fs_exists_at(root)); - sp_must_ok(t, sp_sys_open_dir_s(root.dir, root.sub, 0, &cur)); + sp_must_ok(t, sp_fs_open_dir_at(root, &cur)); sp_for(level, DEEP_LEVELS / 2) { sp_sys_fd_t next = SP_SYS_INVALID_FD; sp_must_ok(t, sp_sys_open_dir_s(cur, sp_str_lit(DEEP_NAME), 0, &next)); sp_sys_close(cur); cur = next; } - sp_expect_ok(t, sp_fs_remove_dir_at((sp_path_t) { .dir = cur, .sub = sp_str_lit(DEEP_NAME) })); + sp_expect_ok(t, sp_fs_remove_dir_at(sp_path_at(cur, sp_str_lit(DEEP_NAME)))); sp_sys_close(cur); sp_expect_ok(t, sp_fs_remove_dir_at(root)); diff --git a/test/fs/remove/sim.c b/test/fs/remove/sim.c index 55149ae8..412a8df6 100644 --- a/test/fs/remove/sim.c +++ b/test/fs/remove/sim.c @@ -118,7 +118,7 @@ static const test_t tests [] = { sp_test_each(fs, remove_sim, test_t, tests, .serial = true) { sim_t s = sp_zero; sim_begin(&s, it->dirs); - sp_err_t err = sp_fs_remove_dir_at((sp_path_t) { .dir = sp_sys_get_root(0), .sub = sp_str_lit("T") }); + sp_err_t err = sp_fs_remove_dir_at(sp_path_at(sp_sys_get_root(0), sp_str_lit("T"))); sim_end(&s); sp_expect_err_eq(t, err, it->expect.err); diff --git a/test/io/file.c b/test/io/file.c index 92d3b748..98136d1b 100644 --- a/test/io/file.c +++ b/test/io/file.c @@ -132,14 +132,14 @@ UTEST_F(io, file_reader_buffered_seek_discards_buffer) { } UTEST_F(io, file_reader_nonexistent) { - sp_path_t path = { .dir = ut.file.dir, .sub = sp_str_lit("nonexistent.file") }; + sp_path_t path = sp_path_at(ut.file.dir, sp_str_lit("nonexistent.file")); sp_io_file_reader_t r = sp_zero; EXPECT_EQ(sp_io_file_reader_from_path_at(&r, path), SP_ERR_SYS_NOT_FOUND); sp_io_file_reader_close(&r); } UTEST_F(io, file_writer_nonexistent_dir) { - sp_path_t path = { .dir = ut.file.dir, .sub = sp_str_lit("missing-dir/nested.file") }; + sp_path_t path = sp_path_at(ut.file.dir, sp_str_lit("missing-dir/nested.file")); sp_io_file_writer_t w = sp_zero; EXPECT_EQ(sp_io_file_writer_from_path_at(&w, path), SP_ERR_SYS_NOT_FOUND); } @@ -370,7 +370,7 @@ UTEST_F(io, file_to_file_copy) { EXPECT_EQ(sp_io_write(&sw.base, source, sizeof(source), SP_NULLPTR), SP_OK); sp_io_file_writer_close(&sw); - sp_path_t dst_path = { .dir = ut.file.dir, .sub = sp_str_lit("file_to_file_copy.dst") }; + sp_path_t dst_path = sp_path_at(ut.file.dir, sp_str_lit("file_to_file_copy.dst")); sp_io_file_reader_t r = sp_zero; sp_io_file_reader_from_path_at(&r, ut.file); @@ -402,7 +402,7 @@ UTEST_F(io, stream_file_to_file_copy) { EXPECT_EQ(sp_io_write(&sw.base, source, sizeof(source), SP_NULLPTR), SP_OK); sp_io_file_writer_close(&sw); - sp_path_t dst_path = { .dir = ut.file.dir, .sub = sp_str_lit("stream_file_to_file_copy.dst") }; + sp_path_t dst_path = sp_path_at(ut.file.dir, sp_str_lit("stream_file_to_file_copy.dst")); sp_sys_fd_t file = SP_SYS_INVALID_FD; ASSERT_EQ(sp_sys_open_s(ut.file.dir, ut.file.sub, SP_SYS_OPEN_MODE_RO, 0, &file), SP_OK); From c3d233e6bdbde1cc4e52956ae8f4789a6eddd3e6 Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Tue, 29 Sep 2026 04:20:33 -0400 Subject: [PATCH 54/72] report the test dir with its root label --- sp/sp_test.h | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/sp/sp_test.h b/sp/sp_test.h index 12ad7bf4..1d55a77f 100644 --- a/sp/sp_test.h +++ b/sp/sp_test.h @@ -1024,6 +1024,17 @@ sp_mem_t sp_test_mem(sp_test_t* t) { return t->tracked_mem; } +static sp_str_t sp_test_path_str(sp_mem_t mem, sp_path_t path) { + c8 buf [SP_PATH_MAX]; + u64 len = 0; + for (s32 it = 0; sp_sys_get_root(it) != SP_SYS_INVALID_FD; it++) { + if (sp_sys_get_root(it) != path.dir) continue; + if (sp_sys_get_root_label(it, buf, sizeof(buf), &len)) break; + return sp_fs_join_path(mem, sp_str(buf, (u32)len), path.sub); + } + return sp_str_copy(mem, path.sub); +} + sp_path_t sp_test_dir(sp_test_t* t) { if (sp_str_empty(t->dir.sub)) { sp_str_t leaf = sp_str_replace_c8(t->mem, sp_cstr_as_str(t->instance->name), '/', '_'); @@ -1471,7 +1482,7 @@ static void sp_test_report(sp_test_t* t, sp_tty_t* term, sp_test_status_t status } if (!sp_str_empty(t->dir.sub)) { - sp_tty_fmt(term, " {.gray} {}\n", sp_fmt_cstr("dir"), sp_fmt_str(t->dir.sub)); + sp_tty_fmt(term, " {.gray} {}\n", sp_fmt_cstr("dir"), sp_fmt_str(sp_test_path_str(t->mem, t->dir))); } sp_da_for(t->failures, it) { From a966e151edec8bf39c3d291ee3f0f73a25efdfea Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Tue, 29 Sep 2026 04:23:13 -0400 Subject: [PATCH 55/72] rooted goldens --- sp/sp_test.h | 138 ++++++++++------------ spn.toml | 2 +- test/test/resolve.c | 55 --------- tools/windows/sp/spn/amalgamation.vcxproj | 2 +- 4 files changed, 63 insertions(+), 134 deletions(-) delete mode 100644 test/test/resolve.c diff --git a/sp/sp_test.h b/sp/sp_test.h index 1d55a77f..e29a2423 100644 --- a/sp/sp_test.h +++ b/sp/sp_test.h @@ -405,8 +405,6 @@ SP_API bool sp_test_mem_eq(sp_test_t* t, const void* lhs, const void* rhs SP_API bool sp_test_strs_eq(sp_test_t* t, const sp_str_t* actual, u64 count, const c8* const* expect, const c8* sa, const c8* se, sp_str_t file, u32 line); SP_API void sp_test_golden(sp_test_t* t, sp_str_t path, sp_str_t actual, sp_str_t file, u32 line); -SP_API void sp_test_golden_abs(sp_test_t* t, sp_str_t path, sp_str_t actual, sp_str_t file, u32 line); -SP_API sp_da(sp_str_t) sp_test_resolve_roots(sp_mem_t mem, sp_str_t cwd, sp_str_t exe_dir); typedef struct { sp_atomic_s32_t state; @@ -673,8 +671,7 @@ SP_API sp_str_t sp_test_value_opaque(sp_test_t* t, ...); #define sp_expect_err_eq(T, A, B) sp_test_err_eq(T, A, B, #A, #B, sp_test_soft) #define sp_must_err_eq(T, A, B) sp_test_err_eq(T, A, B, #A, #B, sp_test_stop) -#define sp_expect_golden(T, PATH, ACTUAL) sp_test_golden(T, PATH, ACTUAL, sp_cstr_as_str(__FILE__), (u32)__LINE__) -#define sp_expect_golden_abs(T, PATH, ACTUAL) sp_test_golden_abs(T, PATH, ACTUAL, sp_cstr_as_str(__FILE__), (u32)__LINE__) +#define sp_expect_golden(T, PATH, ACTUAL) sp_test_golden(T, PATH, ACTUAL, sp_cstr_as_str(__FILE__), (u32)__LINE__) #endif @@ -745,7 +742,7 @@ struct sp_test_runner_t { sp_da(const c8*) updated; sp_path_t dir_root; sp_test_once_t symlinks; - sp_str_t golden_root; + sp_path_t golden_root; bool update; sp_test_keep_t keep; bool kept; @@ -1077,111 +1074,99 @@ bool sp_test_symlinks_supported(sp_test_t* t) { //////////// // GOLDEN // //////////// -sp_da(sp_str_t) sp_test_resolve_roots(sp_mem_t mem, sp_str_t cwd, sp_str_t exe_dir) { - sp_da(sp_str_t) roots = sp_da_new(mem, sp_str_t); - - sp_str_t anchors [] = { cwd, exe_dir }; - sp_carr_for(anchors, at) { - if (sp_str_empty(anchors[at])) continue; - - sp_str_t dir = sp_fs_normalize_path(mem, anchors[at]); - while (!sp_str_empty(dir)) { - bool seen = false; - sp_da_for(roots, it) { - if (sp_str_equal(roots[it], dir)) { seen = true; break; } - } - if (!seen) sp_da_push(roots, dir); +static sp_path_t sp_test_golden_search(sp_mem_t mem, sp_path_t dir, sp_str_t file) { + while (true) { + if (sp_fs_is_target_file_at(sp_path_join(mem, dir, file))) return dir; - sp_str_t parent = sp_fs_parent_path(dir); - if (sp_str_equal(parent, dir)) break; - dir = parent; - } + sp_path_t parent = sp_path_parent(mem, dir); + sp_sys_file_meta_t from = sp_zero; + sp_sys_file_meta_t to = sp_zero; + if (sp_sys_get_path_metadata_s(dir.dir, dir.sub, &from)) return sp_zero_s(sp_path_t); + if (sp_sys_get_path_metadata_s(parent.dir, parent.sub, &to)) return sp_zero_s(sp_path_t); + if (from.id == to.id && from.device == to.device) return sp_zero_s(sp_path_t); + dir = parent; } - - return roots; } -static sp_str_t sp_test_golden_root(sp_test_t* t, sp_str_t file) { +static sp_path_t sp_test_golden_root(sp_test_t* t, sp_str_t file) { sp_test_runner_t* runner = t->runner; sp_mutex_lock(&runner->mutex); - sp_str_t root = runner->golden_root; + sp_path_t root = runner->golden_root; sp_mutex_unlock(&runner->mutex); - if (!sp_str_empty(root)) return root; + if (!sp_str_empty(root.sub)) return root; - sp_str_t cwd = sp_fs_get_cwd_path(t->mem); - sp_str_t exe_dir = sp_fs_parent_path(sp_fs_get_exe_path(t->mem)); - sp_da(sp_str_t) roots = sp_test_resolve_roots(t->mem, cwd, exe_dir); + sp_path_t anchors [2] = { sp_path_at_cwd(sp_str_lit(".")) }; + sp_str_t exe = sp_fs_get_exe_path(t->mem); + if (!sp_str_empty(exe)) anchors[1] = sp_path_resolve(sp_fs_parent_path(exe)); - sp_da_for(roots, it) { - if (!sp_fs_is_target_file(sp_fs_join_path(t->mem, roots[it], file))) continue; - root = roots[it]; - break; + sp_carr_for(anchors, it) { + if (sp_str_empty(anchors[it].sub)) continue; + root = sp_test_golden_search(t->mem, anchors[it], file); + if (!sp_str_empty(root.sub)) break; } - if (sp_str_empty(root)) return root; + if (sp_str_empty(root.sub)) return root; sp_mutex_lock(&runner->mutex); - if (sp_str_empty(runner->golden_root)) { - runner->golden_root = sp_str_copy(runner->mem, root); + if (sp_str_empty(runner->golden_root.sub)) { + runner->golden_root = sp_path_at(root.dir, sp_str_copy(runner->mem, root.sub)); } root = runner->golden_root; sp_mutex_unlock(&runner->mutex); return root; } -static void sp_test_golden_at(sp_test_t* t, sp_str_t path, sp_str_t actual, sp_str_t file, u32 line) { - sp_str_t actual_path = sp_test_format(t, "{}.actual", sp_fmt_str(path)); +static void sp_test_golden_at(sp_test_t* t, sp_path_t path, sp_str_t actual, sp_str_t file, u32 line) { + sp_str_t shown = sp_test_path_str(t->mem, path); + sp_path_t actual_path = sp_path_at(path.dir, sp_test_format(t, "{}.actual", sp_fmt_str(path.sub))); if (t->runner->update) { - sp_str_t parent = sp_fs_parent_path(path); - if (!sp_str_empty(parent) && !sp_fs_exists(parent)) { - sp_fs_create_dir(parent); - } + sp_fs_create_dir_at(sp_path_parent(t->mem, path)); - sp_err_t err = sp_fs_create_file_str(path, actual); + sp_err_t err = sp_fs_create_file_str_at(path, actual); if (err) { sp_test_record(t, (sp_test_failure_t) { .file = file, .line = line, .message = sp_test_format(t, "failed to update golden {}: err {}", - sp_fmt_str(path), sp_fmt_int(err)), + sp_fmt_str(shown), sp_fmt_int(err)), }); return; } - if (sp_fs_exists(actual_path)) sp_fs_remove_file(actual_path); + if (sp_fs_exists_at(actual_path)) sp_fs_remove_file_at(actual_path); t->updated++; return; } - if (!sp_fs_exists(path)) { + if (!sp_fs_exists_at(path)) { sp_test_record(t, (sp_test_failure_t) { .file = file, .line = line, .message = sp_test_format(t, "golden {} does not exist; run with --update to create it", - sp_fmt_str(path)), + sp_fmt_str(shown)), }); return; } sp_str_t want = sp_zero; - sp_err_t err = sp_io_read_file(t->mem, path, &want); + sp_err_t err = sp_io_read_file_at(t->mem, path, &want); if (err) { sp_test_record(t, (sp_test_failure_t) { .file = file, .line = line, .message = sp_test_format(t, "failed to read golden {}: err {}", - sp_fmt_str(path), sp_fmt_int(err)), + sp_fmt_str(shown), sp_fmt_int(err)), }); return; } if (sp_str_equal(want, actual)) { - if (sp_fs_exists(actual_path)) sp_fs_remove_file(actual_path); + if (sp_fs_exists_at(actual_path)) sp_fs_remove_file_at(actual_path); return; } - sp_fs_create_file_str(actual_path, actual); + sp_fs_create_file_str_at(actual_path, actual); sp_da(sp_str_t) want_lines = sp_str_split_c8(t->mem, want, '\n'); sp_da(sp_str_t) got_lines = sp_str_split_c8(t->mem, actual, '\n'); @@ -1199,29 +1184,22 @@ static void sp_test_golden_at(sp_test_t* t, sp_str_t path, sp_str_t actual, sp_s .file = file, .line = line, .message = sp_test_format(t, "golden mismatch at {}:{}; wrote {}", - sp_fmt_str(path), sp_fmt_uint(diff + 1), sp_fmt_str(actual_path)), + sp_fmt_str(shown), sp_fmt_uint(diff + 1), sp_fmt_str(sp_test_path_str(t->mem, actual_path))), .expected = sp_test_format(t, "{.quote}", sp_fmt_str(want_line)), .actual = sp_test_format(t, "{.quote}", sp_fmt_str(got_line)), }); } void sp_test_golden(sp_test_t* t, sp_str_t path, sp_str_t actual, sp_str_t file, u32 line) { - if (sp_fs_is_absolute_for(path, SP_FS_PATH_WINDOWS)) { - sp_test_record(t, (sp_test_failure_t) { - .file = file, - .line = line, - .message = sp_test_format(t, "golden path {} is absolute; paths resolve against the calling source dir (use sp_expect_golden_abs)", - sp_fmt_str(path)), - }); - return; - } - file = sp_fs_normalize_path(t->mem, file); - sp_str_t src = file; - if (!sp_fs_is_absolute_for(file, SP_FS_PATH_WINDOWS)) { - sp_str_t root = sp_test_golden_root(t, file); - if (sp_str_empty(root)) { + sp_path_t src = sp_zero; + if (sp_fs_is_absolute_for(file, SP_FS_PATH_WINDOWS)) { + src = sp_path_resolve(file); + } + else { + sp_path_t root = sp_test_golden_root(t, file); + if (sp_str_empty(root.sub)) { sp_test_record(t, (sp_test_failure_t) { .file = file, .line = line, @@ -1230,24 +1208,23 @@ void sp_test_golden(sp_test_t* t, sp_str_t path, sp_str_t actual, sp_str_t file, }); return; } - src = sp_fs_join_path(t->mem, root, file); + src = sp_path_join(t->mem, root, file); } - if (!sp_fs_is_target_file(src)) { + if (src.dir == SP_SYS_INVALID_FD || !sp_fs_is_target_file_at(src)) { sp_test_record(t, (sp_test_failure_t) { .file = file, .line = line, .message = sp_test_format(t, "cannot locate {}; goldens resolve against the calling source dir (--golden-root overrides)", - sp_fmt_str(src)), + sp_fmt_str(sp_test_path_str(t->mem, src))), }); return; } - sp_test_golden_at(t, sp_fs_join_path(t->mem, sp_fs_parent_path(src), path), actual, file, line); -} - -void sp_test_golden_abs(sp_test_t* t, sp_str_t path, sp_str_t actual, sp_str_t file, u32 line) { - sp_test_golden_at(t, path, actual, file, line); + sp_path_t golden = sp_fs_is_absolute(path) ? + sp_path_resolve(path) : + sp_path_join(t->mem, sp_path_parent(t->mem, src), path); + sp_test_golden_at(t, golden, actual, file, line); } @@ -2038,7 +2015,14 @@ s32 sp_test_main(s32 argc, const c8** argv, const sp_test_entry_t* entries) { sp_str_t golden_root = sp_zero; if (golden.env) golden_root = sp_cstr_as_str(golden.env); if (golden.opt) golden_root = sp_cstr_as_str(golden.opt); - if (!sp_str_empty(golden_root)) runner->golden_root = sp_fs_normalize_path(runner->mem, golden_root); + if (!sp_str_empty(golden_root)) { + runner->golden_root = sp_path_resolve(sp_fs_normalize_path(runner->mem, golden_root)); + if (runner->golden_root.dir == SP_SYS_INVALID_FD) { + sp_fmt_io(&runner->out.base, "no root serves {.quote}\n", sp_fmt_str(golden_root)); + sp_io_flush(&runner->out.base); + return 1; + } + } sp_mutex_init(&runner->mutex); sp_io_stream_writer_from_fd(&runner->out, sp_sys_stdout, SP_IO_CLOSE_MODE_NONE); diff --git a/spn.toml b/spn.toml index 51d4a067..daa37b50 100644 --- a/spn.toml +++ b/spn.toml @@ -14,7 +14,7 @@ name = "amalgamation" source = [ "test/main.c", "test/test/axis.c", "test/test/filter.c", "test/test/matrix.c", - "test/test/resolve.c", "test/test/sweep.c", "test/test/tracking.c", + "test/test/sweep.c", "test/test/tracking.c", "test/sys/sys.c", "test/sys/open.c", "test/sys/path.c", "test/sys/futex.c", "test/sys/fd.c", "test/sys/fd_path.c", "test/sys/nanosleep.c", "test/sys/timestamps.c", "test/sys/perms.c", "test/sys/self_path.c", "test/sys/pipe/data.c", "test/sys/pipe/blocking.c", "test/sys/transfer.c", "test/sys/readlink.c", diff --git a/test/test/resolve.c b/test/test/resolve.c deleted file mode 100644 index 522a55d0..00000000 --- a/test/test/resolve.c +++ /dev/null @@ -1,55 +0,0 @@ -#include "sp/sp_test.h" - -typedef struct { - const c8* name; - sp_fs_path_kind_t kind; - const c8* cwd; - const c8* exe_dir; - const c8* expect [16]; -} resolve_case_t; - -static const resolve_case_t resolve_cases [] = { - { - .name = "disjoint", - .cwd = "/a/b", - .exe_dir = "/x/y", - .expect = { "/a/b", "/a", "/", "/x/y", "/x" }, - }, - { - .name = "overlap", - .cwd = "/x/repo", - .exe_dir = "/x/repo/build/debug", - .expect = { "/x/repo", "/x", "/", "/x/repo/build/debug", "/x/repo/build" }, - }, - { - .name = "windows", - .kind = SP_FS_PATH_WINDOWS, - .cwd = "C:\\repo", - .exe_dir = "C:\\repo\\build", - .expect = { "C:/repo", "C:/", "C:/repo/build" }, - }, - { - .name = "trailing", - .cwd = "/a/b/", - .exe_dir = "", - .expect = { "/a/b", "/a", "/" }, - }, - { - .name = "empty", - .cwd = "", - .exe_dir = "/x", - .expect = { "/x", "/" }, - }, -}; - -sp_test_each(golden, resolve, resolve_case_t, resolve_cases) { - if (it->kind != SP_FS_PATH_POSIX && it->kind != sp_os_get_path_kind()) { - return sp_test_skip(t, "paths of another kind"); - } - - sp_da(sp_str_t) roots = sp_test_resolve_roots(sp_test_arena(t), - sp_cstr_as_str(it->cwd), sp_cstr_as_str(it->exe_dir)); - - sp_must_strs_eq(t, roots, sp_da_size(roots), it->expect); - return SP_OK; -} diff --git a/tools/windows/sp/spn/amalgamation.vcxproj b/tools/windows/sp/spn/amalgamation.vcxproj index 628702f8..26b715a1 100644 --- a/tools/windows/sp/spn/amalgamation.vcxproj +++ b/tools/windows/sp/spn/amalgamation.vcxproj @@ -18,7 +18,7 @@ amalgamation 10.0 amalgamation - ..\..\..\..\test\main.c;..\..\..\..\test\test\axis.c;..\..\..\..\test\test\filter.c;..\..\..\..\test\test\matrix.c;..\..\..\..\test\test\resolve.c;..\..\..\..\test\test\sweep.c;..\..\..\..\test\test\tracking.c;..\..\..\..\test\sys\sys.c;..\..\..\..\test\sys\open.c;..\..\..\..\test\sys\path.c;..\..\..\..\test\sys\futex.c;..\..\..\..\test\sys\fd.c;..\..\..\..\test\sys\fd_path.c;..\..\..\..\test\sys\nanosleep.c;..\..\..\..\test\sys\timestamps.c;..\..\..\..\test\sys\perms.c;..\..\..\..\test\sys\self_path.c;..\..\..\..\test\sys\pipe\data.c;..\..\..\..\test\sys\pipe\blocking.c;..\..\..\..\test\sys\transfer.c;..\..\..\..\test\sys\readlink.c;..\..\..\..\test\sys\socket\data.c;..\..\..\..\test\sys\socket\listen.c;..\..\..\..\test\sys\socket\desc.c;..\..\..\..\test\sys\tty\contract.c;..\..\..\..\test\sys\tty\mode.c;..\..\..\..\test\sys\wait\event.c;..\..\..\..\test\sys\wait\wait.c;..\..\..\..\test\sys\inherit.c;..\..\..\..\test\fs\dir\*.c;..\..\..\..\test\fs\it\*.c;..\..\..\..\test\fs\remove\*.c;..\..\..\..\test\fs\wtf8\*.c;..\..\..\..\test\fs\*.c;..\..\..\..\test\fs\windows\nt_path.c;..\..\..\..\test\msvc\*.c;..\..\..\..\test\mem\uninitialized.c;..\..\..\..\test\atomic.c;..\..\..\..\test\sync\*.c;..\..\..\..\test\sync\os\*.c + ..\..\..\..\test\main.c;..\..\..\..\test\test\axis.c;..\..\..\..\test\test\filter.c;..\..\..\..\test\test\matrix.c;..\..\..\..\test\test\sweep.c;..\..\..\..\test\test\tracking.c;..\..\..\..\test\sys\sys.c;..\..\..\..\test\sys\open.c;..\..\..\..\test\sys\path.c;..\..\..\..\test\sys\futex.c;..\..\..\..\test\sys\fd.c;..\..\..\..\test\sys\fd_path.c;..\..\..\..\test\sys\nanosleep.c;..\..\..\..\test\sys\timestamps.c;..\..\..\..\test\sys\perms.c;..\..\..\..\test\sys\self_path.c;..\..\..\..\test\sys\pipe\data.c;..\..\..\..\test\sys\pipe\blocking.c;..\..\..\..\test\sys\transfer.c;..\..\..\..\test\sys\readlink.c;..\..\..\..\test\sys\socket\data.c;..\..\..\..\test\sys\socket\listen.c;..\..\..\..\test\sys\socket\desc.c;..\..\..\..\test\sys\tty\contract.c;..\..\..\..\test\sys\tty\mode.c;..\..\..\..\test\sys\wait\event.c;..\..\..\..\test\sys\wait\wait.c;..\..\..\..\test\sys\inherit.c;..\..\..\..\test\fs\dir\*.c;..\..\..\..\test\fs\it\*.c;..\..\..\..\test\fs\remove\*.c;..\..\..\..\test\fs\wtf8\*.c;..\..\..\..\test\fs\*.c;..\..\..\..\test\fs\windows\nt_path.c;..\..\..\..\test\msvc\*.c;..\..\..\..\test\mem\uninitialized.c;..\..\..\..\test\atomic.c;..\..\..\..\test\sync\*.c;..\..\..\..\test\sync\os\*.c ..\..\..\..\sp.h;..\..\..\..\sp\sp_test.h false From ff49ce7cfbd4b7c1c8730b032c7ae3ee28f882b2 Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Tue, 29 Sep 2026 04:23:13 -0400 Subject: [PATCH 56/72] golden harness tests --- test/test/golden.c | 148 +++++++++++++++++++++++++++++++++++++++++++++ test/test/main.c | 10 +++ 2 files changed, 158 insertions(+) create mode 100644 test/test/golden.c diff --git a/test/test/golden.c b/test/test/golden.c new file mode 100644 index 00000000..28d30dea --- /dev/null +++ b/test/test/golden.c @@ -0,0 +1,148 @@ +#include "sp/sp_test.h" + +typedef struct { + const c8* status; + s32 exit_code; + const c8* golden; + const c8* actual; +} expect_t; + +typedef struct { + const c8* name; + const c8* golden; + const c8* cwd; + const c8* root; + const c8* env; + bool update; + bool absolute; + expect_t expect; +} test_t; + +static const test_t tests [] = { + { + .name = "match", + .golden = "A", + .root = ".", + .expect = { .status = "ok", .golden = "A" }, + }, + { + .name = "mismatch", + .golden = "B", + .root = ".", + .expect = { .status = "failed", .exit_code = 1, .golden = "B", .actual = "A" }, + }, + { + .name = "missing", + .root = ".", + .expect = { .status = "failed", .exit_code = 1 }, + }, + { + .name = "update", + .root = ".", + .update = true, + .expect = { .status = "updated", .golden = "A" }, + }, + { + .name = "update_clears_actual", + .golden = "B", + .root = ".", + .update = true, + .expect = { .status = "updated", .golden = "A" }, + }, + { + .name = "env_root", + .golden = "A", + .env = ".", + .expect = { .status = "ok", .golden = "A" }, + }, + { + .name = "flag_over_env", + .golden = "A", + .cwd = "D", + .env = "nowhere", + .root = "..", + .expect = { .status = "ok", .golden = "A" }, + }, + { + .name = "search_from_cwd", + .golden = "A", + .cwd = "D", + .expect = { .status = "ok", .golden = "A" }, + }, + { + .name = "search_walks_up", + .golden = "A", + .cwd = "D/E/F", + .expect = { .status = "ok", .golden = "A" }, + }, + { + .name = "absolute_file", + .golden = "A", + .cwd = "D", + .absolute = true, + .expect = { .status = "ok", .golden = "A" }, + }, +}; + +sp_test_each(runner, golden, test_t, tests) { + sp_test_skip_on_wasm(); + sp_test_skip_on_freestanding(); + + if (it->absolute) sp_test_skip_without_absolute_names(); + + sp_mem_t mem = sp_test_arena(t); + sp_path_t sandbox = sp_test_dir(t); + sp_str_t exe = sp_fs_get_exe_path(mem); + + sp_path_t src = sp_path_join(mem, sandbox, sp_str_lit("S/main.c")); + sp_path_t golden = sp_path_join(mem, sandbox, sp_str_lit("S/G")); + sp_path_t actual = sp_path_join(mem, sandbox, sp_str_lit("S/G.actual")); + sp_must_ok(t, sp_fs_create_dir_at(sp_path_parent(mem, src))); + sp_must_ok(t, sp_fs_create_file_at(src)); + sp_str_t file = it->absolute ? sp_fs_canonicalize_path_at(mem, src) : sp_str_lit("S/main.c"); + if (it->golden) sp_must_ok(t, sp_fs_create_file_cstr_at(golden, it->golden)); + if (it->update) sp_must_ok(t, sp_fs_create_file_cstr_at(actual, "stale")); + + sp_str_t cwd = it->cwd ? sp_fs_join_path(mem, sandbox.sub, sp_cstr_as_str(it->cwd)) : sandbox.sub; + sp_must_ok(t, sp_fs_create_dir_at(sp_path_at(sandbox.dir, cwd))); + + sp_ps_config_t config = { + .command = exe, + .args = { sp_str_lit("child"), sp_str_lit("--filter"), sp_str_lit("child.golden"), sp_str_lit("--dir"), sp_str_lit("T") }, + .cwd = cwd, + .env = { + .extra = { + { .key = sp_str_lit("SP_TEST_GOLDEN_ROOT"), .value = sp_cstr_as_str(it->env ? it->env : "") }, + { .key = sp_str_lit("SP_TEST_CHILD_FILE"), .value = file }, + }, + }, + .io = { + .in = { .mode = SP_PS_IO_MODE_NULL }, + .err = { .mode = SP_PS_IO_MODE_NULL }, + }, + }; + if (it->root) { + sp_ps_config_add_arg(mem, &config, sp_str_lit("--golden-root")); + sp_ps_config_add_arg(mem, &config, sp_cstr_as_str(it->root)); + } + if (it->update) sp_ps_config_add_arg(mem, &config, sp_str_lit("--update")); + + sp_ps_output_t out = sp_ps_run(mem, config); + sp_test_kv(t, "stdout", out.out); + sp_must_ok(t, out.error); + sp_expect_eq(t, out.status.exit_code, it->expect.exit_code); + sp_expect(t, sp_str_contains(out.out, sp_test_format(t, "\nchild.golden {} ", sp_fmt_cstr(it->expect.status)))); + + sp_str_t content = sp_zero; + sp_expect_eq(t, sp_fs_exists_at(golden), it->expect.golden != SP_NULLPTR); + if (it->expect.golden) { + sp_expect_ok(t, sp_io_read_file_at(mem, golden, &content)); + sp_expect_str_eq_c(t, content, it->expect.golden); + } + sp_expect_eq(t, sp_fs_exists_at(actual), it->expect.actual != SP_NULLPTR); + if (it->expect.actual) { + sp_expect_ok(t, sp_io_read_file_at(mem, actual, &content)); + sp_expect_str_eq_c(t, content, it->expect.actual); + } + return SP_OK; +} diff --git a/test/test/main.c b/test/test/main.c index 426cfae4..03a45261 100644 --- a/test/test/main.c +++ b/test/test/main.c @@ -73,6 +73,7 @@ static sp_err_t child_pass(sp_test_t* t); static sp_err_t child_fail(sp_test_t* t); static sp_err_t child_leak(sp_test_t* t); static sp_err_t child_skip(sp_test_t* t); +static sp_err_t child_golden(sp_test_t* t); static sp_err_t child_each(sp_test_t* t, const void* arg); static const sp_test_entry_t child_entries [] = { @@ -96,6 +97,10 @@ static const sp_test_entry_t child_entries [] = { .kind = SP_TEST_ENTRY_TEST, .decl = { .suite = "child", .name = "declared", .kind = SP_TEST_DECL_FN, .fn = child_pass, .keep = true }, }, + { + .kind = SP_TEST_ENTRY_TEST, + .decl = { .suite = "child", .name = "golden", .kind = SP_TEST_DECL_FN, .fn = child_golden }, + }, { .kind = SP_TEST_ENTRY_TEST, .decl = { @@ -143,6 +148,11 @@ static sp_err_t child_skip(sp_test_t* t) { return sp_test_skip(t, "S"); } +static sp_err_t child_golden(sp_test_t* t) { + sp_test_golden(t, sp_str_lit("G"), sp_str_lit("A"), sp_os_env_get(sp_str_lit("SP_TEST_CHILD_FILE")), (u32)__LINE__); + return SP_OK; +} + static sp_err_t child_each(sp_test_t* t, const void* arg) { SP_UNUSED(arg); sp_must_ok(t, child_touch(t)); From 80b5ad7987afb2c3a06c4ddfec4fea3b2e22a6f1 Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Tue, 29 Sep 2026 04:28:04 -0400 Subject: [PATCH 57/72] sys: add dir_it_rewind --- sp.h | 34 +++++++++++++++++++++++++++++++--- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/sp.h b/sp.h index d8048937..c72fdae7 100644 --- a/sp.h +++ b/sp.h @@ -1538,6 +1538,7 @@ SP_API sp_err_t sp_sys_set_times_s(sp_sys_fd_t fd, sp_str_t path, sp_sys_time SP_API sp_err_t sp_sys_dir_it_open(sp_sys_fd_t fd, sp_sys_dir_it_t* out); SP_API sp_err_t sp_sys_dir_it_read(sp_sys_dir_it_t* it, sp_mem_buffer_t* buf); SP_API sp_err_t sp_sys_dir_it_parse(sp_sys_dir_it_t* it, sp_mem_buffer_t* buf, u64* cursor, sp_sys_dir_entry_t* out); +SP_API sp_err_t sp_sys_dir_it_rewind(sp_sys_dir_it_t* it); SP_API sp_err_t sp_sys_dir_it_close(sp_sys_dir_it_t* it); SP_API bool sp_sys_is_read_only(sp_sys_file_perms_t perms); SP_API void sp_sys_set_read_only(sp_sys_file_perms_t* perms, bool read_only); @@ -1620,6 +1621,7 @@ typedef struct { sp_err_t (*dir_it_open)(sp_sys_fd_t fd, sp_sys_dir_it_t* out); sp_err_t (*dir_it_read)(sp_sys_dir_it_t* it, sp_mem_buffer_t* buf); sp_err_t (*dir_it_parse)(sp_sys_dir_it_t* it, sp_mem_buffer_t* buf, u64* cursor, sp_sys_dir_entry_t* out); + sp_err_t (*dir_it_rewind)(sp_sys_dir_it_t* it); sp_err_t (*dir_it_close)(sp_sys_dir_it_t* it); } sp_sys_vtable_t; @@ -1695,6 +1697,7 @@ SP_API sp_err_t sp_sys_chdir_p(const c8* path, u32 len); SP_API sp_err_t sp_sys_dir_it_open_p(sp_sys_fd_t fd, sp_sys_dir_it_t* out); SP_API sp_err_t sp_sys_dir_it_read_p(sp_sys_dir_it_t* it, sp_mem_buffer_t* buf); SP_API sp_err_t sp_sys_dir_it_parse_p(sp_sys_dir_it_t* it, sp_mem_buffer_t* buf, u64* cursor, sp_sys_dir_entry_t* out); +SP_API sp_err_t sp_sys_dir_it_rewind_p(sp_sys_dir_it_t* it); SP_API sp_err_t sp_sys_dir_it_close_p(sp_sys_dir_it_t* it); SP_API const sp_sys_vtable_t sp_sys_vtable_platform; @@ -3139,10 +3142,11 @@ SP_API sp_path_t sp_path_parent(sp_mem_t mem, sp_path_t path); A filesystem iterator sp_sys_dir_it_t is the OS level iteration state (usually, just the handle to - the top level directory, but on WASM also a cookie and on macOS a DIR*). The - syscalls are strictly about answering: + the top level directory, but on WASM also a cookie, on Windows a pending + restart, and on macOS a DIR*). The syscalls are strictly about answering: - Asking the OS fill a buffer with the next N entries for the handle - Parsing those opaque bytes into entries + - Rewinding the handle to the first entry That can be driven however you want! There's no concept of the "next" entry, traversing into child directories, or buffer ownership. Syscalls. @@ -5792,6 +5796,7 @@ const sp_sys_vtable_t sp_sys_vtable_platform = { .dir_it_open = sp_sys_dir_it_open_p, .dir_it_read = sp_sys_dir_it_read_p, .dir_it_parse = sp_sys_dir_it_parse_p, + .dir_it_rewind = sp_sys_dir_it_rewind_p, .dir_it_close = sp_sys_dir_it_close_p, }; @@ -6189,6 +6194,10 @@ sp_err_t sp_sys_dir_it_parse(sp_sys_dir_it_t* it, sp_mem_buffer_t* buf, u64* cur return (sp_rt.vt->dir_it_parse)(it, buf, cursor, out); } +sp_err_t sp_sys_dir_it_rewind(sp_sys_dir_it_t* it) { + return (sp_rt.vt->dir_it_rewind)(it); +} + sp_err_t sp_sys_dir_it_close(sp_sys_dir_it_t* it) { return (sp_rt.vt->dir_it_close)(it); } @@ -13878,6 +13887,8 @@ sp_err_t sp_sys_dir_it_open_p(sp_sys_fd_t fd, sp_sys_dir_it_t* out) { sp_err_t sp_sys_dir_it_read_p(sp_sys_dir_it_t* it, sp_mem_buffer_t* buf) { buf->len = 0; #if defined(SP_WIN32) + u8 restart = (u8)it->state; + it->state = 0; sp_nt_io_status_block_t iosb = sp_zero; sp_nt_status_t status = SP_NT(NtQueryDirectoryFile)( (void*)(intptr_t)it->fd, @@ -13885,7 +13896,7 @@ sp_err_t sp_sys_dir_it_read_p(sp_sys_dir_it_t* it, sp_mem_buffer_t* buf) { &iosb, buf->data, (u32)(buf->capacity - SP_SYS_DIR_WIN32_SCRATCH), SP_NT_FILE_DIRECTORY_INFORMATION, - 0, SP_NULLPTR, 0); + 0, SP_NULLPTR, restart); if (status == SP_NT_STATUS_NO_MORE_FILES) return SP_OK; if (status == SP_NT_STATUS_NO_SUCH_FILE) return SP_OK; if (!SP_NT_SUCCESS(status)) return sp_sys_err_from_nt(status); @@ -13978,6 +13989,23 @@ sp_err_t sp_sys_dir_it_parse_p(sp_sys_dir_it_t* it, sp_mem_buffer_t* buf, u64* c #endif } +sp_err_t sp_sys_dir_it_rewind_p(sp_sys_dir_it_t* it) { +#if defined(SP_WIN32) + it->state = true; + return SP_OK; +#elif defined(SP_LINUX) + return sp_sys_lseek(it->fd, 0, SP_IO_SEEK_SET, SP_NULLPTR); +#elif defined(SP_MACOS) || defined(SP_COSMO) + rewinddir((DIR*)(intptr_t)it->state); + return SP_OK; +#elif defined(SP_WASM) + it->state = (s64)__WASI_DIRCOOKIE_START; + return SP_OK; +#else + #error "sp_sys_dir_it_rewind" +#endif +} + sp_err_t sp_sys_dir_it_close_p(sp_sys_dir_it_t* it) { #if defined(SP_WIN32) || defined(SP_LINUX) || defined(SP_WASM) return sp_sys_close(it->fd); From ba77c5fa3015e0c8457765ae4538d65b7b95f8c4 Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Tue, 29 Sep 2026 04:48:38 -0400 Subject: [PATCH 58/72] test: sim lists dirs positionally --- test/fs/sim.c | 41 ++++++++++++++++++++++++++++------------- test/fs/sim.h | 7 ++++++- 2 files changed, 34 insertions(+), 14 deletions(-) diff --git a/test/fs/sim.c b/test/fs/sim.c index bf4faced..748d326e 100644 --- a/test/fs/sim.c +++ b/test/fs/sim.c @@ -26,15 +26,6 @@ static const sim_entry_t* find_entry(sp_str_t path) { return SP_NULLPTR; } -static u32 dir_len(const sim_dir_t* dir) { - u32 n = 0; - sp_carr_for(dir->entries, it) { - if (!dir->entries[it].name) break; - n++; - } - return n; -} - static s64 slot_of(sp_sys_fd_t fd) { return (s64)fd - FD_BASE; } @@ -52,6 +43,10 @@ static void record(sp_str_t path, sim_op_t op) { sim_removed_t* out = &active->removed[active->num_removed++]; sp_str_copy_to(path, out->path, SIM_MAX_REMOVED_PATH); out->op = op; + + const sim_dir_t* dir = find_dir(sp_fs_parent_path(path)); + const sim_entry_t* entry = find_entry(path); + if (entry) active->gone[dir - active->dirs][entry - dir->entries] = true; } static sp_err_t open_dir(sp_sys_fd_t fd, const c8* path, u32 len, u32 flags, sp_sys_fd_t* out) { @@ -84,15 +79,29 @@ static sp_err_t dir_it_open(sp_sys_fd_t fd, sp_sys_dir_it_t* out) { } static sp_err_t dir_it_read(sp_sys_dir_it_t* it, sp_mem_buffer_t* buf) { + active->count.reads++; + if (active->count.reads > SIM_MAX_READS) return SP_ERR_SYS_TIMED_OUT; sim_open_t* slot = &active->opened[it->state]; - if (slot->dir->read) return slot->dir->read; - buf->len = slot->served ? 0 : dir_len(slot->dir); - slot->served = true; + const sim_dir_t* dir = slot->dir; + if (dir->read) return dir->read; + + u32 batch = dir->batch ? dir->batch : SIM_MAX_ENTRIES; + u32 position = 0; + buf->len = 0; + sp_carr_for(dir->entries, e) { + if (!dir->entries[e].name) break; + if (active->gone[dir - active->dirs][e]) continue; + if (position++ < slot->position) continue; + if (buf->len == batch) break; + slot->listed[buf->len++] = e; + } + slot->position += (u32)buf->len; return SP_OK; } static sp_err_t dir_it_parse(sp_sys_dir_it_t* it, sp_mem_buffer_t* buf, u64* cursor, sp_sys_dir_entry_t* out) { - const sim_entry_t* entry = &active->opened[it->state].dir->entries[*cursor]; + sim_open_t* slot = &active->opened[it->state]; + const sim_entry_t* entry = &slot->dir->entries[slot->listed[*cursor]]; *cursor += 1; u32 len = (u32)sp_cstr_len(entry->name); @@ -106,6 +115,11 @@ static sp_err_t dir_it_parse(sp_sys_dir_it_t* it, sp_mem_buffer_t* buf, u64* cur return SP_OK; } +static sp_err_t dir_it_rewind(sp_sys_dir_it_t* it) { + active->opened[it->state].position = 0; + return SP_OK; +} + static sp_err_t dir_it_close(sp_sys_dir_it_t* it) { active->count.closes++; return SP_OK; @@ -161,6 +175,7 @@ void sim_begin(sim_t* sim, const sim_dir_t* dirs) { sim->vt.dir_it_open = dir_it_open; sim->vt.dir_it_read = dir_it_read; sim->vt.dir_it_parse = dir_it_parse; + sim->vt.dir_it_rewind = dir_it_rewind; sim->vt.dir_it_close = dir_it_close; sim->vt.get_link_metadata = get_link_metadata; sim->vt.unlink = unlink_entry; diff --git a/test/fs/sim.h b/test/fs/sim.h index 0be9e894..8f70eaac 100644 --- a/test/fs/sim.h +++ b/test/fs/sim.h @@ -8,6 +8,7 @@ #define SIM_MAX_OPENS 4 #define SIM_MAX_REMOVED 8 #define SIM_MAX_REMOVED_PATH 64 +#define SIM_MAX_READS 32 typedef enum { SIM_OP_UNLINK, @@ -27,12 +28,14 @@ typedef struct { sp_err_t it_open; sp_err_t read; sp_err_t rmdir; + u32 batch; sim_entry_t entries [SIM_MAX_ENTRIES]; } sim_dir_t; typedef struct { const sim_dir_t* dir; - bool served; + u32 position; + u32 listed [SIM_MAX_ENTRIES]; } sim_open_t; typedef struct { @@ -42,6 +45,7 @@ typedef struct { u32 fd_closes; u32 rmdirs; u32 nofollow; + u32 reads; } sim_count_t; typedef struct { @@ -55,6 +59,7 @@ typedef struct { sim_open_t opened [SIM_MAX_OPENS]; sim_removed_t removed [SIM_MAX_REMOVED]; u32 num_removed; + bool gone [SIM_MAX_DIRS][SIM_MAX_ENTRIES]; sp_sys_vtable_t vt; const sp_sys_vtable_t* saved; } sim_t; From e4c1e44183532f06f329204758a9b25b7ec2d62e Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Tue, 29 Sep 2026 04:48:38 -0400 Subject: [PATCH 59/72] fs: rescan dirs after removing entries --- sp.h | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/sp.h b/sp.h index c72fdae7..e20cef1c 100644 --- a/sp.h +++ b/sp.h @@ -3157,6 +3157,7 @@ typedef struct { sp_sys_dir_it_t dir; sp_mem_buffer_t buf; u64 cursor; + bool rescan; } sp_fs_dir_it_t; typedef struct { @@ -20636,8 +20637,13 @@ sp_err_t sp_fs_dir_it_next(sp_fs_dir_it_t* it, sp_fs_dir_entry_t* out) { // Pull another chunk from the kernel and advance the cursor if (it->cursor >= it->buf.len) { sp_try(sp_sys_dir_it_read(&it->dir, &it->buf)); - if (!it->buf.len) return SP_OK; it->cursor = 0; + if (!it->buf.len) { + if (!it->rescan) return SP_OK; + it->rescan = false; + sp_try(sp_sys_dir_it_rewind(&it->dir)); + continue; + } } sp_sys_dir_entry_t entry = sp_zero; @@ -20743,6 +20749,18 @@ sp_err_t sp_fs_it_enter(sp_fs_it_t* it) { return sp_fs_it_push(it, it->at, it->entry.path.len, SP_SYS_OPEN_DIR_NO_FOLLOW); } +SP_PRIVATE sp_err_t sp_fs_it_unlink(sp_fs_it_t* it) { + sp_try(sp_sys_unlink_s(it->at.dir, it->at.sub)); + sp_da_back(it->stack)->dir.rescan = true; + return SP_OK; +} + +SP_PRIVATE sp_err_t sp_fs_it_rmdir(sp_fs_it_t* it) { + sp_try(sp_sys_rmdir_s(it->at.dir, it->at.sub)); + sp_da_back(it->stack)->dir.rescan = true; + return SP_OK; +} + bool sp_fs_it_walk(sp_fs_it_t* it) { while (sp_fs_it_next(it)) { if (it->yield == SP_FS_IT_LEAVE) continue; @@ -20882,7 +20900,7 @@ sp_err_t sp_fs_remove_dir_at(sp_path_t path) { while (!err && sp_fs_it_next(&it)) { switch (it.yield) { case SP_FS_IT_LEAVE: { - err = sp_sys_rmdir_s(it.at.dir, it.at.sub); + err = sp_fs_it_rmdir(&it); break; } case SP_FS_IT_ENTRY: { @@ -20890,10 +20908,10 @@ sp_err_t sp_fs_remove_dir_at(sp_path_t path) { // absent d_type can't strand an entry if (it.entry.kind == SP_FS_KIND_DIR) { err = sp_fs_it_enter(&it); - if (err == SP_ERR_SYS_NOT_DIR || err == SP_ERR_SYS_LOOP) err = sp_sys_unlink_s(it.at.dir, it.at.sub); + if (err == SP_ERR_SYS_NOT_DIR || err == SP_ERR_SYS_LOOP) err = sp_fs_it_unlink(&it); } else { - err = sp_sys_unlink_s(it.at.dir, it.at.sub); + err = sp_fs_it_unlink(&it); if (err == SP_ERR_SYS_IS_DIR) err = sp_fs_it_enter(&it); } break; From f8fcdb2112842ab1165bb87569692ed1f5cda760 Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Tue, 29 Sep 2026 05:03:09 -0400 Subject: [PATCH 60/72] test: dir rescan restarts listing --- test/fs/dir/os.c | 41 ++++++++++++++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/test/fs/dir/os.c b/test/fs/dir/os.c index dc97c11a..ce9321ba 100644 --- a/test/fs/dir/os.c +++ b/test/fs/dir/os.c @@ -20,6 +20,7 @@ typedef struct { const c8* dir; bool relative; u32 bulk; + bool rescan; entry_t expect [FS_MAX_PATHS]; } test_t; @@ -85,8 +86,35 @@ static const test_t tests [] = { .name = "refills_across_batches", .bulk = 96, }, + { + .name = "rescan_restarts_listing", + .setup = { + { "A" }, + }, + .rescan = true, + .expect = { + { "A", SP_FS_KIND_FILE }, + }, + }, }; +static void expect_listing(sp_test_t* t, sp_fs_dir_it_t* iter, fs_match_t* matches, u32 n) { + sp_for(m, n) { + matches[m].seen = false; + } + + sp_err_t walk = SP_OK; + while (true) { + sp_fs_dir_entry_t entry = sp_zero; + walk = sp_fs_dir_it_next(iter, &entry); + if (walk) break; + if (!entry.name.data) break; + fs_match(t, matches, n, entry.name, entry.kind); + } + sp_expect_ok(t, walk); + fs_match_finish(t, matches, n); +} + sp_test_each(fs, dir, test_t, tests) { if (fs_setup_needs_symlinks(it->setup)) sp_test_skip_without_symlinks(); @@ -120,18 +148,13 @@ sp_test_each(fs, dir, test_t, tests) { sp_expect_ok(t, open_err); if (!open_err) { - sp_err_t walk = SP_OK; - while (true) { - sp_fs_dir_entry_t entry = sp_zero; - walk = sp_fs_dir_it_next(&iter, &entry); - if (walk) break; - if (!entry.name.data) break; - fs_match(t, matches, n, entry.name, entry.kind); + expect_listing(t, &iter, matches, n); + if (it->rescan) { + iter.rescan = true; + expect_listing(t, &iter, matches, n); } - sp_expect_ok(t, walk); sp_expect_ok(t, sp_fs_dir_it_close(&iter)); } - fs_match_finish(t, matches, n); if (sandbox_fd != SP_SYS_INVALID_FD) sp_sys_close(sandbox_fd); return SP_OK; From 662e4ba5b162f5836a60b7d1b38015594472f096 Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Tue, 29 Sep 2026 05:03:09 -0400 Subject: [PATCH 61/72] test: remove across shifted listings --- test/fs/remove/os.c | 20 ++++++++++++++++++++ test/fs/remove/sim.c | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/test/fs/remove/os.c b/test/fs/remove/os.c index 9d2a170e..c139cfaf 100644 --- a/test/fs/remove/os.c +++ b/test/fs/remove/os.c @@ -15,6 +15,7 @@ typedef struct { bool dir; const c8* cwd; const c8* path; + u32 bulk; expect_t expect; } test_t; @@ -72,6 +73,20 @@ static const test_t tests [] = { }, }, }, + { + .name = "dir_refills_across_batches", + .setup = { + { "A", FS_SETUP_DIR }, + }, + .dir = true, + .path = "A", + .bulk = 96, + .expect = { + .paths = { + { .path = "A" }, + }, + }, + }, { .name = "dir_multi_component_sub", .setup = { @@ -267,6 +282,11 @@ sp_test_each(fs, remove, test_t, tests) { fs_apply_setup(t, sandbox, it->setup); sp_path_t base = it->cwd ? sp_path_join(mem, sandbox, sp_cstr_as_str(it->cwd)) : sandbox; + sp_path_t target = sp_path_join(mem, base, sp_cstr_as_str(it->path)); + sp_for(b, it->bulk) { + sp_str_t name = sp_fmt(mem, "R{}", sp_fmt_uint(b)).value; + sp_must_ok(t, sp_fs_create_file_at(sp_path_join(mem, target, name))); + } sp_sys_fd_t dir = SP_SYS_INVALID_FD; sp_must_ok(t, sp_fs_open_dir_at(base, &dir)); diff --git a/test/fs/remove/sim.c b/test/fs/remove/sim.c index 412a8df6..15355c29 100644 --- a/test/fs/remove/sim.c +++ b/test/fs/remove/sim.c @@ -102,6 +102,40 @@ static const test_t tests [] = { .rmdirs = 1, }, }, + { + .name = "shifted_listing_is_rescanned", + .dirs = { + { .path = "T", .batch = 1, .entries = { { "A", SP_FS_KIND_FILE }, { "B", SP_FS_KIND_FILE } } }, + }, + .expect = { + .removed = { { "T/A", SIM_OP_UNLINK }, { "T/B", SIM_OP_UNLINK }, { "T", SIM_OP_RMDIR } }, + .opens = 1, + .rmdirs = 1, + }, + }, + { + .name = "removed_subdir_shifts_parent_listing", + .dirs = { + { .path = "T", .batch = 1, .entries = { { "B", SP_FS_KIND_DIR }, { "C", SP_FS_KIND_FILE } } }, + { .path = "T/B" }, + }, + .expect = { + .removed = { { "T/B", SIM_OP_RMDIR }, { "T/C", SIM_OP_UNLINK }, { "T", SIM_OP_RMDIR } }, + .opens = 2, + .rmdirs = 2, + }, + }, + { + .name = "unlink_error_propagates_without_rescan", + .dirs = { + { .path = "T", .entries = { { "A", SP_FS_KIND_FILE }, { "B", SP_FS_KIND_FILE, .unlink = SP_ERR_SYS_ACCESS_DENIED } } }, + }, + .expect = { + .err = SP_ERR_SYS_ACCESS_DENIED, + .removed = { { "T/A", SIM_OP_UNLINK } }, + .opens = 1, + }, + }, { .name = "read_error_fails_fast_and_unwinds", .dirs = { From 94085bf070fe405b25d0d4eb51a949d9d6a454ff Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Tue, 29 Sep 2026 13:07:22 -0400 Subject: [PATCH 62/72] create_parent --- sp.h | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/sp.h b/sp.h index e20cef1c..fd06a337 100644 --- a/sp.h +++ b/sp.h @@ -3104,6 +3104,7 @@ SP_API sp_tm_epoch_t sp_fs_get_mod_time(sp_str_t path); SP_API sp_tm_epoch_t sp_fs_get_mod_time_at(sp_path_t path); SP_API sp_err_t sp_fs_create_dir(sp_str_t path); SP_API sp_err_t sp_fs_create_dir_at(sp_path_t path); +SP_API sp_err_t sp_fs_create_parent_at(sp_path_t path); SP_API sp_err_t sp_fs_create_file(sp_str_t path); SP_API sp_err_t sp_fs_create_file_at(sp_path_t path); SP_API sp_err_t sp_fs_create_file_str(sp_str_t path, sp_str_t str); @@ -20525,8 +20526,6 @@ sp_err_t sp_io_read_file(sp_mem_t mem, sp_str_t path, sp_str_t* content) { return sp_io_read_file_at(mem, sp_path_at_cwd(path), content); } -SP_PRIVATE sp_err_t sp_fs_create_parent_at(sp_path_t path); - SP_PRIVATE sp_err_t sp_fs_mkdir_at(sp_sys_fd_t dir, sp_str_t path) { sp_err_t err = sp_sys_mkdir_s(dir, path, sp_sys_default_dir_perms); if (!err || err == SP_ERR_SYS_NOT_FOUND) return err; @@ -20949,7 +20948,7 @@ SP_PRIVATE sp_err_t sp_fs_temp_name(sp_str_t dir, sp_str_t name, c8 buf [SP_PATH return SP_OK; } -SP_PRIVATE sp_err_t sp_fs_create_parent_at(sp_path_t path) { +SP_API sp_err_t sp_fs_create_parent_at(sp_path_t path) { sp_str_t parent = sp_fs_parent_path(path.sub); if (sp_str_equal(parent, sp_fs_trim_path(path.sub))) return SP_OK; sp_err_t err = sp_fs_create_dir_at(sp_path_at(path.dir, parent)); From 515f10744acbbabd4bdc27ebc837a6f1e09b04b5 Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Tue, 29 Sep 2026 16:48:28 -0400 Subject: [PATCH 63/72] fix remove_dir --- sp.h | 120 +++++++++++++++----------------------- test/fs/dir/os.c | 41 +++---------- test/fs/it/enter_sim.c | 47 ++++++++++----- test/fs/remove/os.c | 30 ++++++++-- test/fs/remove/sim.c | 127 +++++++++++++++++++++++++++++++++++++---- test/fs/sim.c | 42 +++++++++----- test/fs/sim.h | 6 +- 7 files changed, 263 insertions(+), 150 deletions(-) diff --git a/sp.h b/sp.h index fd06a337..94693bd9 100644 --- a/sp.h +++ b/sp.h @@ -1538,7 +1538,6 @@ SP_API sp_err_t sp_sys_set_times_s(sp_sys_fd_t fd, sp_str_t path, sp_sys_time SP_API sp_err_t sp_sys_dir_it_open(sp_sys_fd_t fd, sp_sys_dir_it_t* out); SP_API sp_err_t sp_sys_dir_it_read(sp_sys_dir_it_t* it, sp_mem_buffer_t* buf); SP_API sp_err_t sp_sys_dir_it_parse(sp_sys_dir_it_t* it, sp_mem_buffer_t* buf, u64* cursor, sp_sys_dir_entry_t* out); -SP_API sp_err_t sp_sys_dir_it_rewind(sp_sys_dir_it_t* it); SP_API sp_err_t sp_sys_dir_it_close(sp_sys_dir_it_t* it); SP_API bool sp_sys_is_read_only(sp_sys_file_perms_t perms); SP_API void sp_sys_set_read_only(sp_sys_file_perms_t* perms, bool read_only); @@ -1621,7 +1620,6 @@ typedef struct { sp_err_t (*dir_it_open)(sp_sys_fd_t fd, sp_sys_dir_it_t* out); sp_err_t (*dir_it_read)(sp_sys_dir_it_t* it, sp_mem_buffer_t* buf); sp_err_t (*dir_it_parse)(sp_sys_dir_it_t* it, sp_mem_buffer_t* buf, u64* cursor, sp_sys_dir_entry_t* out); - sp_err_t (*dir_it_rewind)(sp_sys_dir_it_t* it); sp_err_t (*dir_it_close)(sp_sys_dir_it_t* it); } sp_sys_vtable_t; @@ -1697,7 +1695,6 @@ SP_API sp_err_t sp_sys_chdir_p(const c8* path, u32 len); SP_API sp_err_t sp_sys_dir_it_open_p(sp_sys_fd_t fd, sp_sys_dir_it_t* out); SP_API sp_err_t sp_sys_dir_it_read_p(sp_sys_dir_it_t* it, sp_mem_buffer_t* buf); SP_API sp_err_t sp_sys_dir_it_parse_p(sp_sys_dir_it_t* it, sp_mem_buffer_t* buf, u64* cursor, sp_sys_dir_entry_t* out); -SP_API sp_err_t sp_sys_dir_it_rewind_p(sp_sys_dir_it_t* it); SP_API sp_err_t sp_sys_dir_it_close_p(sp_sys_dir_it_t* it); SP_API const sp_sys_vtable_t sp_sys_vtable_platform; @@ -3026,6 +3023,7 @@ SP_API void sp_tm_epoch_to_iso8601_w(sp_io_writer_t* io, sp_tm_epoc // @fs @filesystem #define SP_PATH_MAX 4096 #define SP_FS_IT_BUF_SIZE SP_SYS_DIR_MIN_BUF +#define SP_FS_IT_MAX_DEPTH (SP_PATH_MAX / 2 + 1) typedef enum { SP_FS_ATOMIC_REPLACE, @@ -3143,11 +3141,10 @@ SP_API sp_path_t sp_path_parent(sp_mem_t mem, sp_path_t path); A filesystem iterator sp_sys_dir_it_t is the OS level iteration state (usually, just the handle to - the top level directory, but on WASM also a cookie, on Windows a pending - restart, and on macOS a DIR*). The syscalls are strictly about answering: + the top level directory, but on WASM also a cookie and on macOS a DIR*). The + syscalls are strictly about answering: - Asking the OS fill a buffer with the next N entries for the handle - Parsing those opaque bytes into entries - - Rewinding the handle to the first entry That can be driven however you want! There's no concept of the "next" entry, traversing into child directories, or buffer ownership. Syscalls. @@ -3158,7 +3155,6 @@ typedef struct { sp_sys_dir_it_t dir; sp_mem_buffer_t buf; u64 cursor; - bool rescan; } sp_fs_dir_it_t; typedef struct { @@ -5798,7 +5794,6 @@ const sp_sys_vtable_t sp_sys_vtable_platform = { .dir_it_open = sp_sys_dir_it_open_p, .dir_it_read = sp_sys_dir_it_read_p, .dir_it_parse = sp_sys_dir_it_parse_p, - .dir_it_rewind = sp_sys_dir_it_rewind_p, .dir_it_close = sp_sys_dir_it_close_p, }; @@ -6196,10 +6191,6 @@ sp_err_t sp_sys_dir_it_parse(sp_sys_dir_it_t* it, sp_mem_buffer_t* buf, u64* cur return (sp_rt.vt->dir_it_parse)(it, buf, cursor, out); } -sp_err_t sp_sys_dir_it_rewind(sp_sys_dir_it_t* it) { - return (sp_rt.vt->dir_it_rewind)(it); -} - sp_err_t sp_sys_dir_it_close(sp_sys_dir_it_t* it) { return (sp_rt.vt->dir_it_close)(it); } @@ -13889,8 +13880,6 @@ sp_err_t sp_sys_dir_it_open_p(sp_sys_fd_t fd, sp_sys_dir_it_t* out) { sp_err_t sp_sys_dir_it_read_p(sp_sys_dir_it_t* it, sp_mem_buffer_t* buf) { buf->len = 0; #if defined(SP_WIN32) - u8 restart = (u8)it->state; - it->state = 0; sp_nt_io_status_block_t iosb = sp_zero; sp_nt_status_t status = SP_NT(NtQueryDirectoryFile)( (void*)(intptr_t)it->fd, @@ -13898,7 +13887,7 @@ sp_err_t sp_sys_dir_it_read_p(sp_sys_dir_it_t* it, sp_mem_buffer_t* buf) { &iosb, buf->data, (u32)(buf->capacity - SP_SYS_DIR_WIN32_SCRATCH), SP_NT_FILE_DIRECTORY_INFORMATION, - 0, SP_NULLPTR, restart); + 0, SP_NULLPTR, 0); if (status == SP_NT_STATUS_NO_MORE_FILES) return SP_OK; if (status == SP_NT_STATUS_NO_SUCH_FILE) return SP_OK; if (!SP_NT_SUCCESS(status)) return sp_sys_err_from_nt(status); @@ -13991,23 +13980,6 @@ sp_err_t sp_sys_dir_it_parse_p(sp_sys_dir_it_t* it, sp_mem_buffer_t* buf, u64* c #endif } -sp_err_t sp_sys_dir_it_rewind_p(sp_sys_dir_it_t* it) { -#if defined(SP_WIN32) - it->state = true; - return SP_OK; -#elif defined(SP_LINUX) - return sp_sys_lseek(it->fd, 0, SP_IO_SEEK_SET, SP_NULLPTR); -#elif defined(SP_MACOS) || defined(SP_COSMO) - rewinddir((DIR*)(intptr_t)it->state); - return SP_OK; -#elif defined(SP_WASM) - it->state = (s64)__WASI_DIRCOOKIE_START; - return SP_OK; -#else - #error "sp_sys_dir_it_rewind" -#endif -} - sp_err_t sp_sys_dir_it_close_p(sp_sys_dir_it_t* it) { #if defined(SP_WIN32) || defined(SP_LINUX) || defined(SP_WASM) return sp_sys_close(it->fd); @@ -20636,13 +20608,8 @@ sp_err_t sp_fs_dir_it_next(sp_fs_dir_it_t* it, sp_fs_dir_entry_t* out) { // Pull another chunk from the kernel and advance the cursor if (it->cursor >= it->buf.len) { sp_try(sp_sys_dir_it_read(&it->dir, &it->buf)); + if (!it->buf.len) return SP_OK; it->cursor = 0; - if (!it->buf.len) { - if (!it->rescan) return SP_OK; - it->rescan = false; - sp_try(sp_sys_dir_it_rewind(&it->dir)); - continue; - } } sp_sys_dir_entry_t entry = sp_zero; @@ -20743,23 +20710,11 @@ bool sp_fs_it_next(sp_fs_it_t* it) { } sp_err_t sp_fs_it_enter(sp_fs_it_t* it) { - sp_assert(it->yield == SP_FS_IT_ENTRY && !sp_str_empty(it->entry.path)); + sp_assert(!sp_str_empty(it->entry.path)); sp_assert(sp_da_back(it->stack)->len < it->entry.path.len); return sp_fs_it_push(it, it->at, it->entry.path.len, SP_SYS_OPEN_DIR_NO_FOLLOW); } -SP_PRIVATE sp_err_t sp_fs_it_unlink(sp_fs_it_t* it) { - sp_try(sp_sys_unlink_s(it->at.dir, it->at.sub)); - sp_da_back(it->stack)->dir.rescan = true; - return SP_OK; -} - -SP_PRIVATE sp_err_t sp_fs_it_rmdir(sp_fs_it_t* it) { - sp_try(sp_sys_rmdir_s(it->at.dir, it->at.sub)); - sp_da_back(it->stack)->dir.rescan = true; - return SP_OK; -} - bool sp_fs_it_walk(sp_fs_it_t* it) { while (sp_fs_it_next(it)) { if (it->yield == SP_FS_IT_LEAVE) continue; @@ -20833,9 +20788,10 @@ sp_err_t sp_fs_remove_dir(sp_str_t path) { into subdirectories, and unlink them. Fails if this root (i.e. fd + path) does not refer to a directory, and unlinks the root at the end. - This function uses O(depth) memory, and each directory is opened exactly - once. Every path under the root must fit in SP_PATH_MAX; a deeper tree fails - with NAME_TOO_LONG, and can be removed by opening a handle partway down. + This function uses O(depth) memory. A directory is reopened only when its + rmdir reports NOT_EMPTY after a pass that removed one of its entries. Every + path under the root must fit in SP_PATH_MAX; a deeper tree fails with + NAME_TOO_LONG, and can be removed by opening a handle partway down. # SYMLINKS @@ -20879,27 +20835,18 @@ sp_err_t sp_fs_remove_dir(sp_str_t path) { Once */ -sp_err_t sp_fs_remove_dir_at(sp_path_t path) { - sp_str_t name = sp_fs_get_name(path.sub); - if (sp_str_empty(name) || sp_fs_is_dot(name)) return SP_ERR_SYS_INVALID; - - sp_sys_file_meta_t meta = sp_zero; - sp_try(sp_sys_get_link_metadata_s(path.dir, path.sub, &meta)); - switch (meta.kind) { - case SP_FS_KIND_DIR: break; - case SP_FS_KIND_SYMLINK: return sp_sys_unlink_s(path.dir, path.sub); - case SP_FS_KIND_FILE: - case SP_FS_KIND_NONE: return SP_ERR_SYS_NOT_DIR; - } - +SP_PRIVATE sp_err_t sp_fs_remove_pass(sp_path_t path, bool* progressed) { sp_mem_arena_marker_t s = sp_mem_begin_scratch(); sp_fs_it_t it = sp_fs_it_new_at(s.mem, path, SP_SYS_OPEN_DIR_NO_FOLLOW); + bool progress [SP_FS_IT_MAX_DEPTH] = sp_zero; sp_err_t err = it.err; while (!err && sp_fs_it_next(&it)) { + u64 depth = sp_da_size(it.stack); switch (it.yield) { case SP_FS_IT_LEAVE: { - err = sp_fs_it_rmdir(&it); + err = sp_sys_rmdir_s(it.at.dir, it.at.sub); + if (err == SP_ERR_SYS_NOT_EMPTY && progress[depth]) err = sp_fs_it_enter(&it); break; } case SP_FS_IT_ENTRY: { @@ -20907,25 +20854,52 @@ sp_err_t sp_fs_remove_dir_at(sp_path_t path) { // absent d_type can't strand an entry if (it.entry.kind == SP_FS_KIND_DIR) { err = sp_fs_it_enter(&it); - if (err == SP_ERR_SYS_NOT_DIR || err == SP_ERR_SYS_LOOP) err = sp_fs_it_unlink(&it); + if (err == SP_ERR_SYS_NOT_DIR || err == SP_ERR_SYS_LOOP) err = sp_sys_unlink_s(it.at.dir, it.at.sub); } else { - err = sp_fs_it_unlink(&it); + err = sp_sys_unlink_s(it.at.dir, it.at.sub); if (err == SP_ERR_SYS_IS_DIR) err = sp_fs_it_enter(&it); } break; } } + if (!err) { + if (sp_da_size(it.stack) > depth) progress[depth] = false; + else progress[depth - 1] = true; + } if (err == SP_ERR_SYS_NOT_FOUND) err = SP_OK; } if (!err) err = it.err; + *progressed = progress[0]; sp_fs_it_deinit(&it); sp_mem_end_scratch(s); - if (err) return err; + return err; +} + +sp_err_t sp_fs_remove_dir_at(sp_path_t path) { + sp_str_t name = sp_fs_get_name(path.sub); + if (sp_str_empty(name) || sp_fs_is_dot(name)) return SP_ERR_SYS_INVALID; - err = sp_sys_rmdir_s(path.dir, path.sub); - return err == SP_ERR_SYS_NOT_FOUND ? SP_OK : err; + sp_sys_file_meta_t meta = sp_zero; + sp_try(sp_sys_get_link_metadata_s(path.dir, path.sub, &meta)); + switch (meta.kind) { + case SP_FS_KIND_DIR: break; + case SP_FS_KIND_SYMLINK: return sp_sys_unlink_s(path.dir, path.sub); + case SP_FS_KIND_FILE: + case SP_FS_KIND_NONE: return SP_ERR_SYS_NOT_DIR; + } + + bool progressed = false; + while (true) { + bool retry = progressed; + sp_err_t err = sp_fs_remove_pass(path, &progressed); + if (err == SP_ERR_SYS_NOT_FOUND && retry) return SP_OK; + if (err) return err; + err = sp_sys_rmdir_s(path.dir, path.sub); + if (err == SP_ERR_SYS_NOT_EMPTY && progressed) continue; + return err == SP_ERR_SYS_NOT_FOUND ? SP_OK : err; + } } static sp_atomic_s32_t sp_fs_atomic_sequence; diff --git a/test/fs/dir/os.c b/test/fs/dir/os.c index ce9321ba..dc97c11a 100644 --- a/test/fs/dir/os.c +++ b/test/fs/dir/os.c @@ -20,7 +20,6 @@ typedef struct { const c8* dir; bool relative; u32 bulk; - bool rescan; entry_t expect [FS_MAX_PATHS]; } test_t; @@ -86,35 +85,8 @@ static const test_t tests [] = { .name = "refills_across_batches", .bulk = 96, }, - { - .name = "rescan_restarts_listing", - .setup = { - { "A" }, - }, - .rescan = true, - .expect = { - { "A", SP_FS_KIND_FILE }, - }, - }, }; -static void expect_listing(sp_test_t* t, sp_fs_dir_it_t* iter, fs_match_t* matches, u32 n) { - sp_for(m, n) { - matches[m].seen = false; - } - - sp_err_t walk = SP_OK; - while (true) { - sp_fs_dir_entry_t entry = sp_zero; - walk = sp_fs_dir_it_next(iter, &entry); - if (walk) break; - if (!entry.name.data) break; - fs_match(t, matches, n, entry.name, entry.kind); - } - sp_expect_ok(t, walk); - fs_match_finish(t, matches, n); -} - sp_test_each(fs, dir, test_t, tests) { if (fs_setup_needs_symlinks(it->setup)) sp_test_skip_without_symlinks(); @@ -148,13 +120,18 @@ sp_test_each(fs, dir, test_t, tests) { sp_expect_ok(t, open_err); if (!open_err) { - expect_listing(t, &iter, matches, n); - if (it->rescan) { - iter.rescan = true; - expect_listing(t, &iter, matches, n); + sp_err_t walk = SP_OK; + while (true) { + sp_fs_dir_entry_t entry = sp_zero; + walk = sp_fs_dir_it_next(&iter, &entry); + if (walk) break; + if (!entry.name.data) break; + fs_match(t, matches, n, entry.name, entry.kind); } + sp_expect_ok(t, walk); sp_expect_ok(t, sp_fs_dir_it_close(&iter)); } + fs_match_finish(t, matches, n); if (sandbox_fd != SP_SYS_INVALID_FD) sp_sys_close(sandbox_fd); return SP_OK; diff --git a/test/fs/it/enter_sim.c b/test/fs/it/enter_sim.c index f21df3a3..77e236a1 100644 --- a/test/fs/it/enter_sim.c +++ b/test/fs/it/enter_sim.c @@ -7,6 +7,7 @@ typedef struct { const c8* path; sp_err_t err; + sp_fs_it_yield_t yield; } enter_t; typedef struct { @@ -90,15 +91,34 @@ static const test_t tests [] = { .opens = 2, }, }, + { + .name = "enter_at_leave_relists", + .dirs = { + { .path = "T", .entries = { { "B", SP_FS_KIND_DIR } } }, + { .path = "T/B", .entries = { { "D", SP_FS_KIND_FILE } } }, + }, + .enter = { { "T/B" }, { "T/B", .yield = SP_FS_IT_LEAVE } }, + .expect = { + .yields = { + { "T/B", "B", SP_FS_KIND_DIR }, + { "T/B/D", "D", SP_FS_KIND_FILE }, + { "T/B", "B", SP_FS_KIND_DIR, SP_FS_IT_LEAVE }, + { "T/B/D", "D", SP_FS_KIND_FILE }, + { "T/B", "B", SP_FS_KIND_DIR, SP_FS_IT_LEAVE }, + }, + .opens = 3, + }, + }, }; -static const enter_t* find_enter(const test_t* test, sp_str_t path) { +static s32 find_enter(const test_t* test, sp_str_t path, sp_fs_it_yield_t yield, const bool* used) { sp_carr_for(test->enter, e) { const enter_t* enter = &test->enter[e]; if (!enter->path) break; - if (sp_str_equal(sp_cstr_as_str(enter->path), path)) return enter; + if (used[e] || enter->yield != yield) continue; + if (sp_str_equal(sp_cstr_as_str(enter->path), path)) return (s32)e; } - return SP_NULLPTR; + return -1; } sp_test_each(fs, it_enter_sim, test_t, tests, .serial = true) { @@ -109,6 +129,7 @@ sp_test_each(fs, it_enter_sim, test_t, tests, .serial = true) { u32 leaves = 0; u32 produced = 0; + bool used [MAX_YIELDS] = sp_zero; sp_fs_it_t walk = sp_fs_it_new_at(mem, sp_path_at(sp_sys_get_root(0), sp_str_lit("T")), 0); while (sp_fs_it_next(&walk)) { if (produced < MAX_YIELDS && it->expect.yields[produced].path) { @@ -125,17 +146,15 @@ sp_test_each(fs, it_enter_sim, test_t, tests, .serial = true) { } produced++; - switch (walk.yield) { - case SP_FS_IT_LEAVE: { - leaves++; - sp_expect_eq(t, s.count.closes, leaves); - break; - } - case SP_FS_IT_ENTRY: { - const enter_t* enter = find_enter(it, walk.entry.path); - if (enter) sp_expect_err_eq(t, sp_fs_it_enter(&walk), enter->err); - break; - } + if (walk.yield == SP_FS_IT_LEAVE) { + leaves++; + sp_expect_eq(t, s.count.closes, leaves); + } + + s32 enter = find_enter(it, walk.entry.path, walk.yield, used); + if (enter >= 0) { + used[enter] = true; + sp_expect_err_eq(t, sp_fs_it_enter(&walk), it->enter[enter].err); } } sp_expect_ok(t, walk.err); diff --git a/test/fs/remove/os.c b/test/fs/remove/os.c index c139cfaf..0e3d387f 100644 --- a/test/fs/remove/os.c +++ b/test/fs/remove/os.c @@ -9,13 +9,18 @@ typedef struct { fs_expected_path_t paths [FS_MAX_PATHS]; } expect_t; +typedef struct { + u32 count; + const c8* dir; +} bulk_t; + typedef struct { const c8* name; fs_setup_t setup [FS_MAX_SETUP]; bool dir; const c8* cwd; const c8* path; - u32 bulk; + bulk_t bulk; expect_t expect; } test_t; @@ -80,13 +85,29 @@ static const test_t tests [] = { }, .dir = true, .path = "A", - .bulk = 96, + .bulk = { .count = 96 }, .expect = { .paths = { { .path = "A" }, }, }, }, + { + .name = "subdir_refills_across_batches", + .setup = { + { "A", FS_SETUP_DIR }, + { "A/B", FS_SETUP_DIR }, + }, + .dir = true, + .path = "A", + .bulk = { .count = 96, .dir = "B" }, + .expect = { + .paths = { + { .path = "A" }, + { .path = "A/B" }, + }, + }, + }, { .name = "dir_multi_component_sub", .setup = { @@ -283,9 +304,10 @@ sp_test_each(fs, remove, test_t, tests) { sp_path_t base = it->cwd ? sp_path_join(mem, sandbox, sp_cstr_as_str(it->cwd)) : sandbox; sp_path_t target = sp_path_join(mem, base, sp_cstr_as_str(it->path)); - sp_for(b, it->bulk) { + sp_path_t into = it->bulk.dir ? sp_path_join(mem, target, sp_cstr_as_str(it->bulk.dir)) : target; + sp_for(b, it->bulk.count) { sp_str_t name = sp_fmt(mem, "R{}", sp_fmt_uint(b)).value; - sp_must_ok(t, sp_fs_create_file_at(sp_path_join(mem, target, name))); + sp_must_ok(t, sp_fs_create_file_at(sp_path_join(mem, into, name))); } sp_sys_fd_t dir = SP_SYS_INVALID_FD; sp_must_ok(t, sp_fs_open_dir_at(base, &dir)); diff --git a/test/fs/remove/sim.c b/test/fs/remove/sim.c index 15355c29..1b2d654f 100644 --- a/test/fs/remove/sim.c +++ b/test/fs/remove/sim.c @@ -24,7 +24,7 @@ static const test_t tests [] = { { .name = "entry_vanished_is_ok", .dirs = { - { .path = "T", .entries = { { "B", SP_FS_KIND_FILE, .unlink = SP_ERR_SYS_NOT_FOUND }, { "C", SP_FS_KIND_FILE } } }, + { .path = "T", .entries = { { "B", SP_FS_KIND_FILE, .absent = true }, { "C", SP_FS_KIND_FILE } } }, }, .expect = { .removed = { { "T/C", SIM_OP_UNLINK }, { "T", SIM_OP_RMDIR } }, @@ -35,8 +35,7 @@ static const test_t tests [] = { { .name = "dir_vanished_before_open_is_ok", .dirs = { - { .path = "T", .entries = { { "B", SP_FS_KIND_DIR } } }, - { .path = "T/B", .open = SP_ERR_SYS_NOT_FOUND }, + { .path = "T", .entries = { { "B", SP_FS_KIND_DIR, .absent = true } } }, }, .expect = { .removed = { { "T", SIM_OP_RMDIR } }, @@ -55,6 +54,27 @@ static const test_t tests [] = { .rmdirs = 1, }, }, + { + .name = "root_vanished_before_open_is_not_found", + .dirs = { + { .path = "T", .open = SP_ERR_SYS_NOT_FOUND }, + }, + .expect = { + .err = SP_ERR_SYS_NOT_FOUND, + .opens = 1, + }, + }, + { + .name = "root_vanished_before_retry_is_ok", + .dirs = { + { .path = "T", .rmdir = SP_ERR_SYS_NOT_EMPTY, .reopen = SP_ERR_SYS_NOT_FOUND, .entries = { { "A", SP_FS_KIND_FILE } } }, + }, + .expect = { + .removed = { { "T/A", SIM_OP_UNLINK } }, + .opens = 2, + .rmdirs = 1, + }, + }, { .name = "file_hint_that_is_dir_is_walked", .dirs = { @@ -92,7 +112,7 @@ static const test_t tests [] = { }, }, { - .name = "not_empty_propagates_without_rescan", + .name = "not_empty_without_progress_propagates", .dirs = { { .path = "T", .rmdir = SP_ERR_SYS_NOT_EMPTY }, }, @@ -103,16 +123,90 @@ static const test_t tests [] = { }, }, { - .name = "shifted_listing_is_rescanned", + .name = "vanished_entry_is_not_progress", .dirs = { - { .path = "T", .batch = 1, .entries = { { "A", SP_FS_KIND_FILE }, { "B", SP_FS_KIND_FILE } } }, + { .path = "T", .rmdir = SP_ERR_SYS_NOT_EMPTY, .entries = { { "B", SP_FS_KIND_FILE, .absent = true } } }, }, .expect = { - .removed = { { "T/A", SIM_OP_UNLINK }, { "T/B", SIM_OP_UNLINK }, { "T", SIM_OP_RMDIR } }, + .err = SP_ERR_SYS_NOT_EMPTY, .opens = 1, .rmdirs = 1, }, }, + { + .name = "vanished_subdir_at_rmdir_is_not_progress", + .dirs = { + { .path = "T", .rmdir = SP_ERR_SYS_NOT_EMPTY, .entries = { { "B", SP_FS_KIND_DIR } } }, + { .path = "T/B", .rmdir = SP_ERR_SYS_NOT_FOUND, .entries = { { "C", SP_FS_KIND_FILE } } }, + }, + .expect = { + .err = SP_ERR_SYS_NOT_EMPTY, + .removed = { { "T/B/C", SIM_OP_UNLINK } }, + .opens = 2, + .rmdirs = 2, + }, + }, + { + .name = "parent_progress_survives_child_pass", + .dirs = { + { .path = "T", .rmdir = SP_ERR_SYS_NOT_EMPTY, .entries = { { "A", SP_FS_KIND_FILE }, { "B", SP_FS_KIND_DIR } } }, + { .path = "T/B", .rmdir = SP_ERR_SYS_NOT_FOUND }, + }, + .expect = { + .err = SP_ERR_SYS_NOT_EMPTY, + .removed = { { "T/A", SIM_OP_UNLINK } }, + .opens = 3, + .rmdirs = 3, + }, + }, + { + .name = "not_empty_after_progress_retries_once", + .dirs = { + { .path = "T", .rmdir = SP_ERR_SYS_NOT_EMPTY, .entries = { { "A", SP_FS_KIND_FILE } } }, + }, + .expect = { + .err = SP_ERR_SYS_NOT_EMPTY, + .removed = { { "T/A", SIM_OP_UNLINK } }, + .opens = 2, + .rmdirs = 2, + }, + }, + { + .name = "subdir_not_empty_after_progress_retries_once", + .dirs = { + { .path = "T", .entries = { { "B", SP_FS_KIND_DIR } } }, + { .path = "T/B", .rmdir = SP_ERR_SYS_NOT_EMPTY, .entries = { { "C", SP_FS_KIND_FILE } } }, + }, + .expect = { + .err = SP_ERR_SYS_NOT_EMPTY, + .removed = { { "T/B/C", SIM_OP_UNLINK } }, + .opens = 3, + .rmdirs = 2, + }, + }, + { + .name = "shifted_listing_is_reopened", + .dirs = { + { .path = "T", .batch = 1, .entries = { { "A", SP_FS_KIND_FILE }, { "B", SP_FS_KIND_FILE } } }, + }, + .expect = { + .removed = { { "T/A", SIM_OP_UNLINK }, { "T/B", SIM_OP_UNLINK }, { "T", SIM_OP_RMDIR } }, + .opens = 2, + .rmdirs = 2, + }, + }, + { + .name = "shifted_subdir_listing_is_reopened", + .dirs = { + { .path = "T", .entries = { { "B", SP_FS_KIND_DIR } } }, + { .path = "T/B", .batch = 1, .entries = { { "A", SP_FS_KIND_FILE }, { "C", SP_FS_KIND_FILE } } }, + }, + .expect = { + .removed = { { "T/B/A", SIM_OP_UNLINK }, { "T/B/C", SIM_OP_UNLINK }, { "T/B", SIM_OP_RMDIR }, { "T", SIM_OP_RMDIR } }, + .opens = 3, + .rmdirs = 3, + }, + }, { .name = "removed_subdir_shifts_parent_listing", .dirs = { @@ -121,12 +215,12 @@ static const test_t tests [] = { }, .expect = { .removed = { { "T/B", SIM_OP_RMDIR }, { "T/C", SIM_OP_UNLINK }, { "T", SIM_OP_RMDIR } }, - .opens = 2, - .rmdirs = 2, + .opens = 3, + .rmdirs = 3, }, }, { - .name = "unlink_error_propagates_without_rescan", + .name = "unlink_error_propagates_without_retry", .dirs = { { .path = "T", .entries = { { "A", SP_FS_KIND_FILE }, { "B", SP_FS_KIND_FILE, .unlink = SP_ERR_SYS_ACCESS_DENIED } } }, }, @@ -136,6 +230,19 @@ static const test_t tests [] = { .opens = 1, }, }, + { + .name = "rmdir_error_propagates_without_retry", + .dirs = { + { .path = "T", .entries = { { "B", SP_FS_KIND_DIR } } }, + { .path = "T/B", .rmdir = SP_ERR_SYS_ACCESS_DENIED, .entries = { { "C", SP_FS_KIND_FILE } } }, + }, + .expect = { + .err = SP_ERR_SYS_ACCESS_DENIED, + .removed = { { "T/B/C", SIM_OP_UNLINK } }, + .opens = 2, + .rmdirs = 1, + }, + }, { .name = "read_error_fails_fast_and_unwinds", .dirs = { diff --git a/test/fs/sim.c b/test/fs/sim.c index 748d326e..831a8efd 100644 --- a/test/fs/sim.c +++ b/test/fs/sim.c @@ -37,27 +37,46 @@ static sp_str_t resolve(sp_mem_t mem, sp_sys_fd_t fd, const c8* path, u32 len) { return sp_fs_join_path(mem, sp_cstr_as_str(active->opened[slot].dir->path), sp_str(path, len)); } +static void mark_gone(sp_str_t path) { + const sim_dir_t* dir = find_dir(sp_fs_parent_path(path)); + const sim_entry_t* entry = find_entry(path); + if (entry) active->gone[dir - active->dirs][entry - dir->entries] = true; +} + static void record(sp_str_t path, sim_op_t op) { sp_assert(active->num_removed < SIM_MAX_REMOVED); sp_assert(path.len < SIM_MAX_REMOVED_PATH); sim_removed_t* out = &active->removed[active->num_removed++]; sp_str_copy_to(path, out->path, SIM_MAX_REMOVED_PATH); out->op = op; + mark_gone(path); +} - const sim_dir_t* dir = find_dir(sp_fs_parent_path(path)); - const sim_entry_t* entry = find_entry(path); - if (entry) active->gone[dir - active->dirs][entry - dir->entries] = true; +static bool dir_empty(const sim_dir_t* dir) { + sp_carr_for(dir->entries, it) { + const sim_entry_t* entry = &dir->entries[it]; + if (!entry->name) break; + if (entry->absent) continue; + if (!active->gone[dir - active->dirs][it]) return false; + } + return true; } static sp_err_t open_dir(sp_sys_fd_t fd, const c8* path, u32 len, u32 flags, sp_sys_fd_t* out) { active->count.opens++; if (flags & SP_SYS_OPEN_DIR_NO_FOLLOW) active->count.nofollow++; + if (active->count.dirs == SIM_MAX_OPENS) return SP_ERR_SYS_TOO_MANY_FILES; sp_mem_arena_marker_t s = sp_mem_begin_scratch(); - const sim_dir_t* dir = find_dir(resolve(s.mem, fd, path, len)); + sp_str_t full = resolve(s.mem, fd, path, len); + const sim_entry_t* entry = find_entry(full); + const sim_dir_t* dir = find_dir(full); sp_mem_end_scratch(s); + if (entry && entry->absent) return SP_ERR_SYS_NOT_FOUND; if (!dir) return SP_ERR_SYS_NOT_FOUND; if (dir->open) return dir->open; - sp_assert(active->count.dirs < SIM_MAX_OPENS); + sp_for(o, active->count.dirs) { + if (active->opened[o].dir == dir && dir->reopen) return dir->reopen; + } active->opened[active->count.dirs] = (sim_open_t) { .dir = dir }; *out = (sp_sys_fd_t)(FD_BASE + active->count.dirs); active->count.dirs++; @@ -79,8 +98,6 @@ static sp_err_t dir_it_open(sp_sys_fd_t fd, sp_sys_dir_it_t* out) { } static sp_err_t dir_it_read(sp_sys_dir_it_t* it, sp_mem_buffer_t* buf) { - active->count.reads++; - if (active->count.reads > SIM_MAX_READS) return SP_ERR_SYS_TIMED_OUT; sim_open_t* slot = &active->opened[it->state]; const sim_dir_t* dir = slot->dir; if (dir->read) return dir->read; @@ -115,11 +132,6 @@ static sp_err_t dir_it_parse(sp_sys_dir_it_t* it, sp_mem_buffer_t* buf, u64* cur return SP_OK; } -static sp_err_t dir_it_rewind(sp_sys_dir_it_t* it) { - active->opened[it->state].position = 0; - return SP_OK; -} - static sp_err_t dir_it_close(sp_sys_dir_it_t* it) { active->count.closes++; return SP_OK; @@ -133,6 +145,7 @@ static sp_err_t get_link_metadata(sp_sys_fd_t fd, const c8* path, u32 len, sp_sy sp_mem_end_scratch(s); *st = sp_zero_s(sp_sys_file_meta_t); + if (entry && entry->absent) return SP_ERR_SYS_NOT_FOUND; if (entry && entry->stat != SP_FS_KIND_NONE) { st->kind = entry->stat; return SP_OK; @@ -150,7 +163,7 @@ static sp_err_t unlink_entry(sp_sys_fd_t fd, const c8* path, u32 len) { sp_str_t full = resolve(s.mem, fd, path, len); const sim_entry_t* entry = find_entry(full); sp_err_t err = SP_ERR_SYS_NOT_FOUND; - if (entry) err = entry->unlink; + if (entry && !entry->absent) err = entry->unlink; if (entry && !err) record(full, SIM_OP_UNLINK); sp_mem_end_scratch(s); return err; @@ -163,7 +176,9 @@ static sp_err_t remove_dir(sp_sys_fd_t fd, const c8* path, u32 len) { const sim_dir_t* dir = find_dir(full); sp_err_t err = SP_ERR_SYS_NOT_FOUND; if (dir) err = dir->rmdir; + if (dir && !err && !dir_empty(dir)) err = SP_ERR_SYS_NOT_EMPTY; if (dir && !err) record(full, SIM_OP_RMDIR); + if (dir && err == SP_ERR_SYS_NOT_FOUND) mark_gone(full); sp_mem_end_scratch(s); return err; } @@ -175,7 +190,6 @@ void sim_begin(sim_t* sim, const sim_dir_t* dirs) { sim->vt.dir_it_open = dir_it_open; sim->vt.dir_it_read = dir_it_read; sim->vt.dir_it_parse = dir_it_parse; - sim->vt.dir_it_rewind = dir_it_rewind; sim->vt.dir_it_close = dir_it_close; sim->vt.get_link_metadata = get_link_metadata; sim->vt.unlink = unlink_entry; diff --git a/test/fs/sim.h b/test/fs/sim.h index 8f70eaac..fdb60bce 100644 --- a/test/fs/sim.h +++ b/test/fs/sim.h @@ -5,10 +5,9 @@ #define SIM_MAX_DIRS 4 #define SIM_MAX_ENTRIES 4 -#define SIM_MAX_OPENS 4 +#define SIM_MAX_OPENS 8 #define SIM_MAX_REMOVED 8 #define SIM_MAX_REMOVED_PATH 64 -#define SIM_MAX_READS 32 typedef enum { SIM_OP_UNLINK, @@ -20,11 +19,13 @@ typedef struct { sp_fs_kind_t kind; sp_fs_kind_t stat; sp_err_t unlink; + bool absent; } sim_entry_t; typedef struct { const c8* path; sp_err_t open; + sp_err_t reopen; sp_err_t it_open; sp_err_t read; sp_err_t rmdir; @@ -45,7 +46,6 @@ typedef struct { u32 fd_closes; u32 rmdirs; u32 nofollow; - u32 reads; } sim_count_t; typedef struct { From 574d537381a536cc5c70520348d0030c5d32fefc Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Tue, 29 Sep 2026 16:59:38 -0400 Subject: [PATCH 64/72] collect cleanup --- tools/wip/embed.c | 36 +++++++++++++++--------------------- 1 file changed, 15 insertions(+), 21 deletions(-) diff --git a/tools/wip/embed.c b/tools/wip/embed.c index 66366afb..6d5ea21f 100644 --- a/tools/wip/embed.c +++ b/tools/wip/embed.c @@ -32,18 +32,6 @@ s32 embed_main(s32 argc, const c8** argv) { sp_str_t out_hdr = sp_str_view(argv[3]); sp_log("scanning {}", sp_fmt_str(src_dir)); - sp_da(sp_fs_entry_t) files; - if (sp_fs_collect_recursive(mem, src_dir, &files)) { - sp_log("could not read {}", sp_fmt_str(src_dir)); - rc = 1; - goto cleanup; - } - if (sp_da_empty(files)) { - sp_log("no files found in {}", sp_fmt_str(src_dir)); - rc = 1; - goto cleanup; - } - sp_elf_t* elf = sp_elf_new_with_null_section(mem); sp_elf_symtab_new(elf); @@ -52,18 +40,13 @@ s32 embed_main(s32 argc, const c8** argv) { sp_da(embed_entry_t) entries = sp_da_new(mem, embed_entry_t); - sp_da_for(files, it) { - sp_fs_entry_t ent = files[it]; - if (ent.kind == SP_FS_KIND_DIR) { - continue; - } - - u32 skip = src_dir.len + 1; - sp_str_t rel_path = sp_str_sub(ent.path, skip, ent.path.len - skip); + sp_fs_it_t files = sp_fs_it_new(mem, src_dir); + while (sp_fs_it_walk(&files)) { + sp_str_t rel_path = sp_str_copy(mem, files.entry.rel); sp_str_t symbol = symbol_from_path(mem, rel_path); sp_str_t content = sp_zero; - sp_io_read_file(mem, ent.path, &content); + sp_io_read_file(mem, files.entry.path, &content); u64 size = content.len; sp_elf_section_t* symtab = sp_elf_find_section_by_name(elf, sp_str_lit(".symtab")); @@ -106,6 +89,17 @@ s32 embed_main(s32 argc, const c8** argv) { .size = size, })); } + sp_fs_it_deinit(&files); + if (files.err) { + sp_log("could not read {}", sp_fmt_str(src_dir)); + rc = 1; + goto cleanup; + } + if (sp_da_empty(entries)) { + sp_log("no files found in {}", sp_fmt_str(src_dir)); + rc = 1; + goto cleanup; + } { sp_str_t file_path = sp_str_view(argv[4]); From 227a185476e2600cce320cb9843fd9b029bfed1a Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Thu, 1 Oct 2026 03:24:30 -0400 Subject: [PATCH 65/72] cleanup --- sp.h | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/sp.h b/sp.h index 94693bd9..6107dccb 100644 --- a/sp.h +++ b/sp.h @@ -13907,7 +13907,6 @@ sp_err_t sp_sys_dir_it_read_p(sp_sys_dir_it_t* it, sp_mem_buffer_t* buf) { buf->len = d->d_reclen; return SP_OK; #elif defined(SP_WASM) - // @spader this is just a temporary stub! probably wrong __wasi_size_t used = 0; __wasi_errno_t err = sp_wasi_fd_readdir((__wasi_fd_t)it->fd, buf->data, (__wasi_size_t)buf->capacity, (__wasi_dircookie_t)it->state, &used); if (err == SP_WASI_ENOENT) return SP_OK; @@ -13964,7 +13963,6 @@ sp_err_t sp_sys_dir_it_parse_p(sp_sys_dir_it_t* it, sp_mem_buffer_t* buf, u64* c out->kind = sp_sys_dir_dtype_to_kind(d->d_type); return SP_OK; #elif defined(SP_WASM) - // @spader this is just a temporary stub! probably wrong (void)it; __wasi_dirent_t d; sp_mem_copy(&d, buf->data + *cursor, sizeof(d)); @@ -20584,9 +20582,6 @@ sp_err_t sp_fs_remove_file(sp_str_t path) { sp_err_t sp_fs_dir_it_open(sp_fs_dir_it_t* it, sp_path_t path, u32 flags, sp_mem_slice_t buf) { *it = sp_zero_s(sp_fs_dir_it_t); - // @spader wtf? - sp_assert(buf.len >= SP_SYS_DIR_MIN_BUF); - sp_sys_fd_t dir_fd = SP_SYS_INVALID_FD; sp_try(sp_sys_open_dir_s(path.dir, path.sub, flags, &dir_fd)); @@ -20635,8 +20630,6 @@ SP_PRIVATE void sp_fs_it_unwind(sp_fs_it_t* it) { sp_da_clear(it->stack); } -// @spader this is garbage; we have to control all the trimming and "custom" path -// functions, because it smells like we have no idea what inputs we take SP_PRIVATE u32 sp_fs_it_child(sp_fs_it_t* it, u32 prefix) { return prefix + (prefix && !sp_fs_is_sep(it->path[prefix - 1])); } @@ -20817,8 +20810,8 @@ sp_err_t sp_fs_remove_dir(sp_str_t path) { named. For example, iterating D might yield D/M/W. The caller never specified D/M/W. They asked us to remove everything under D, which is satisfied. - The combination of these two give a nice property: NOT_FOUND is returned if - and only if nothing was removed. + The combination of these two give a nice property: NOT_FOUND is returned only + if nothing was removed. ## FAIL FAST From 6dc1f555aa72540d014d4340954b15c4cc1b0478 Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Thu, 1 Oct 2026 03:30:43 -0400 Subject: [PATCH 66/72] fs: stub errors for path getters and canonicalize, pin failing tests --- example/fs/absolute.c | 6 ++- example/io.c | 3 +- sp.h | 108 ++++++++++++++++++++++++----------------- sp/sp_test.h | 8 +-- test/bench/ubench.h | 3 +- test/cli/usage.c | 8 ++- test/fs/canonicalize.c | 32 +++++++----- test/fs/fd_relative.c | 5 +- test/fs/fs.h | 6 ++- test/fs/get_cwd.c | 16 +++--- test/fs/get_exe_path.c | 21 +++++--- test/fs/get_path_sim.c | 62 +++++++++++++++++++++++ test/fs/path_parent.c | 2 +- test/fs/remove/os.c | 17 +++++++ test/fs/remove/sim.c | 35 +++++++++++++ test/fs/system_paths.c | 16 ++++-- test/gdb.c | 5 +- test/leak.c | 5 +- test/ps.c | 10 ++-- test/sys/inherit.c | 4 +- test/sys/path.c | 6 ++- test/sys/readlink.c | 4 +- test/test/dir.c | 3 +- test/test/golden.c | 6 ++- test/test/keep.c | 3 +- test/test/prune.c | 3 +- test/tools/test.h | 8 +-- tools/sp.c | 4 +- 28 files changed, 300 insertions(+), 109 deletions(-) create mode 100644 test/fs/get_path_sim.c diff --git a/example/fs/absolute.c b/example/fs/absolute.c index d16931d1..bf10c5bb 100644 --- a/example/fs/absolute.c +++ b/example/fs/absolute.c @@ -10,9 +10,11 @@ static sp_err_t save(sp_mem_t mem, sp_str_t file, sp_str_t* settings) { s32 run(s32 num_args, const c8** args) { sp_mem_arena_marker_t s = sp_mem_begin_scratch(); - sp_str_t file = sp_fs_join_path(s.mem, sp_fs_get_config_path(s.mem), sp_str_lit("sp/settings.toml")); + sp_str_t config = sp_zero; sp_str_t settings = sp_zero; - sp_err_t err = save(s.mem, file, &settings); + sp_err_t err = sp_fs_get_config_path(s.mem, &config); + sp_str_t file = sp_fs_join_path(s.mem, config, sp_str_lit("sp/settings.toml")); + if (!err) err = save(s.mem, file, &settings); if (err) { sp_log("{.red}: {}", sp_fmt_str(file), sp_fmt_str(sp_err_str(err))); } diff --git a/example/io.c b/example/io.c index cae09c09..b0430bb9 100644 --- a/example/io.c +++ b/example/io.c @@ -11,7 +11,8 @@ s32 run(s32 num_args, const c8** args) { .data = sp_alloc_n(mem, u8, 64), .capacity = 64 }; - sp_str_t exe = sp_fs_get_exe_path(mem); + sp_str_t exe = sp_zero; + sp_fs_get_exe_path(mem, &exe); // sp_io provides utilities for opening a file from a path sp_io_file_reader_from_path(&r, exe); diff --git a/sp.h b/sp.h index 6107dccb..b387dfcc 100644 --- a/sp.h +++ b/sp.h @@ -3124,12 +3124,12 @@ SP_API sp_err_t sp_fs_copy_file_at(sp_path_t from, sp_path_t to, sp_ SP_API sp_err_t sp_fs_copy_tree_at(sp_path_t from, sp_path_t to, sp_fs_atomic_mode_t mode); SP_API sp_err_t sp_fs_open_dir_at(sp_path_t path, sp_sys_fd_t* out); SP_API sp_sys_fd_t sp_fs_get_cwd(); -SP_API sp_str_t sp_fs_get_cwd_path(sp_mem_t mem); -SP_API sp_str_t sp_fs_get_exe_path(sp_mem_t mem); -SP_API sp_str_t sp_fs_get_storage_path(sp_mem_t mem); -SP_API sp_str_t sp_fs_get_config_path(sp_mem_t mem); -SP_API sp_str_t sp_fs_canonicalize_path(sp_mem_t mem, sp_str_t path); -SP_API sp_str_t sp_fs_canonicalize_path_at(sp_mem_t mem, sp_path_t path); +SP_API sp_err_t sp_fs_get_cwd_path(sp_mem_t mem, sp_str_t* path); +SP_API sp_err_t sp_fs_get_exe_path(sp_mem_t mem, sp_str_t* path); +SP_API sp_err_t sp_fs_get_storage_path(sp_mem_t mem, sp_str_t* path); +SP_API sp_err_t sp_fs_get_config_path(sp_mem_t mem, sp_str_t* path); +SP_API sp_err_t sp_fs_canonicalize_path(sp_mem_t mem, sp_str_t path, sp_str_t* canonical); +SP_API sp_err_t sp_fs_canonicalize_path_at(sp_mem_t mem, sp_path_t path, sp_str_t* canonical); SP_API sp_path_t sp_path_at(sp_sys_fd_t dir, sp_str_t sub); SP_API sp_path_t sp_path_at_cwd(sp_str_t sub); SP_API sp_path_t sp_path_resolve(sp_str_t path); @@ -16964,7 +16964,11 @@ void sp_fmon_os_add_dir(sp_fmon_t* monitor, sp_str_t path) { sp_fmon_dir_t dir = sp_zero; dir.overlapped.hEvent = event; dir.handle = (sp_win32_handle_t)handle; - dir.path = sp_fs_canonicalize_path(monitor->mem, path); + if (sp_fs_canonicalize_path(monitor->mem, path, &dir.path)) { + CloseHandle(dir.handle); + CloseHandle(event); + return; + } dir.notify_information = sp_alloc(monitor->mem, SP_FILE_MONITOR_BUFFER_SIZE); sp_mem_zero(dir.notify_information, SP_FILE_MONITOR_BUFFER_SIZE); @@ -16975,14 +16979,16 @@ void sp_fmon_os_add_dir(sp_fmon_t* monitor, sp_str_t path) { void sp_fmon_os_add_file(sp_fmon_t* monitor, sp_str_t file_path) { sp_fmon_os_t* os = monitor->os; - sp_str_t canonical = sp_fs_canonicalize_path(monitor->mem, file_path); + sp_str_t canonical = sp_zero; + if (sp_fs_canonicalize_path(monitor->mem, file_path, &canonical)) return; sp_da_push(os->watch_files, canonical); sp_str_t dir_path = sp_fs_parent_path(canonical); if (dir_path.len > 0) { bool found = false; sp_da_for(os->dirs, i) { - sp_str_t path = sp_fs_canonicalize_path(monitor->mem, os->dirs[i].path); // @spader + sp_str_t path = sp_zero; + if (sp_fs_canonicalize_path(monitor->mem, os->dirs[i].path, &path)) continue; // @spader if (sp_str_equal(path, dir_path)) { found = true; break; @@ -17159,7 +17165,8 @@ void sp_fmon_os_deinit(sp_fmon_t* monitor) { void sp_fmon_os_add_file(sp_fmon_t* monitor, sp_str_t file_path) { sp_fmon_os_t* os = (sp_fmon_os_t*)monitor->os; - sp_str_t canonical = sp_fs_canonicalize_path(monitor->mem, file_path); + sp_str_t canonical = sp_zero; + if (sp_fs_canonicalize_path(monitor->mem, file_path, &canonical)) return; sp_str_ht_insert(os->files, canonical, 1); sp_str_t dir_path = sp_fs_parent_path(canonical); @@ -17408,7 +17415,7 @@ void sp_fmon_os_push_dir(sp_fmon_os_t* os, sp_str_t dir) { void sp_fmon_os_push_file(sp_fmon_os_t* os, sp_str_t file) { sp_mutex_lock(&os->mutex); - sp_da_push(os->watch_files, sp_fs_canonicalize_path(os->mem.watch, file)); + sp_da_push(os->watch_files, sp_str_copy(os->mem.watch, file)); sp_mutex_unlock(&os->mutex); } @@ -17420,30 +17427,34 @@ void sp_fmon_os_add_dir(sp_fmon_t* monitor, sp_str_t path) { void sp_fmon_os_add_file(sp_fmon_t* monitor, sp_str_t path) { sp_fmon_os_t* os = monitor->os; - sp_fmon_os_push_file(os, path); + sp_mem_arena_marker_t scratch = sp_mem_begin_scratch(); - { - sp_mem_arena_marker_t scratch = sp_mem_begin_scratch(); + sp_str_t canonical = sp_zero; + if (sp_fs_canonicalize_path(scratch.mem, path, &canonical)) { + sp_mem_end_scratch(scratch); + return; + } - sp_str_t dir = sp_fs_parent_path(sp_fs_canonicalize_path(path)); + sp_fmon_os_push_file(os, canonical); - bool found = false; - sp_mutex_lock(&os->mutex); - sp_da_for(os->watch_paths, it) { - if (sp_str_equal(os->watch_paths[it], dir)) { - found = true; - break; - } - } - sp_mutex_unlock(&os->mutex); + sp_str_t dir = sp_fs_parent_path(canonical); - if (!found) { - sp_fmon_os_push_dir(os, dir); - sp_fmon_fsevents_recreate_stream(monitor); + bool found = false; + sp_mutex_lock(&os->mutex); + sp_da_for(os->watch_paths, it) { + if (sp_str_equal(os->watch_paths[it], dir)) { + found = true; + break; } + } + sp_mutex_unlock(&os->mutex); - sp_mem_end_scratch(scratch); + if (!found) { + sp_fmon_os_push_dir(os, dir); + sp_fmon_fsevents_recreate_stream(monitor); } + + sp_mem_end_scratch(scratch); } void sp_fmon_os_process_changes(sp_fmon_t* monitor) { @@ -20422,41 +20433,50 @@ SP_PRIVATE sp_str_t sp_fs_canonicalize_to(sp_path_t path, c8 buf [SP_PATH_MAX]) return sp_str(buf, (u32)len); } -sp_str_t sp_fs_canonicalize_path_at(sp_mem_t mem, sp_path_t path) { +sp_err_t sp_fs_canonicalize_path_at(sp_mem_t mem, sp_path_t path, sp_str_t* canonical) { c8 buf [SP_PATH_MAX]; - return sp_str_copy(mem, sp_fs_canonicalize_to(path, buf)); + *canonical = sp_str_copy(mem, sp_fs_canonicalize_to(path, buf)); + return SP_OK; } -sp_str_t sp_fs_canonicalize_path(sp_mem_t mem, sp_str_t path) { - return sp_fs_canonicalize_path_at(mem, sp_path_at_cwd(path)); +sp_err_t sp_fs_canonicalize_path(sp_mem_t mem, sp_str_t path, sp_str_t* canonical) { + return sp_fs_canonicalize_path_at(mem, sp_path_at_cwd(path), canonical); } -sp_str_t sp_fs_get_exe_path(sp_mem_t mem) { +sp_err_t sp_fs_get_exe_path(sp_mem_t mem, sp_str_t* path) { c8 buf[SP_PATH_MAX]; u64 len = 0; - if (sp_sys_get_exe_path(buf, SP_PATH_MAX, &len)) return sp_zero_s(sp_str_t); - return sp_str_copy(mem, sp_str(buf, (u32)len)); + *path = sp_zero_s(sp_str_t); + if (sp_sys_get_exe_path(buf, SP_PATH_MAX, &len)) return SP_OK; + *path = sp_str_copy(mem, sp_str(buf, (u32)len)); + return SP_OK; } -sp_str_t sp_fs_get_cwd_path(sp_mem_t mem) { +sp_err_t sp_fs_get_cwd_path(sp_mem_t mem, sp_str_t* path) { c8 buf[SP_PATH_MAX]; u64 len = 0; - if (sp_sys_get_fd_path(sp_fs_get_cwd(), buf, SP_PATH_MAX, &len)) return sp_zero_s(sp_str_t); - return sp_str_copy(mem, sp_str(buf, (u32)len)); + *path = sp_zero_s(sp_str_t); + if (sp_sys_get_fd_path(sp_fs_get_cwd(), buf, SP_PATH_MAX, &len)) return SP_OK; + *path = sp_str_copy(mem, sp_str(buf, (u32)len)); + return SP_OK; } -sp_str_t sp_fs_get_storage_path(sp_mem_t mem) { +sp_err_t sp_fs_get_storage_path(sp_mem_t mem, sp_str_t* path) { c8 buf[SP_PATH_MAX]; u64 len = 0; - if (sp_sys_get_storage_path(buf, SP_PATH_MAX, &len)) return sp_zero_s(sp_str_t); - return sp_fs_normalize_path(mem, sp_str(buf, (u32)len)); + *path = sp_zero_s(sp_str_t); + if (sp_sys_get_storage_path(buf, SP_PATH_MAX, &len)) return SP_OK; + *path = sp_fs_normalize_path(mem, sp_str(buf, (u32)len)); + return SP_OK; } -sp_str_t sp_fs_get_config_path(sp_mem_t mem) { +sp_err_t sp_fs_get_config_path(sp_mem_t mem, sp_str_t* path) { c8 buf[SP_PATH_MAX]; u64 len = 0; - if (sp_sys_get_config_path(buf, SP_PATH_MAX, &len)) return sp_zero_s(sp_str_t); - return sp_fs_normalize_path(mem, sp_str(buf, (u32)len)); + *path = sp_zero_s(sp_str_t); + if (sp_sys_get_config_path(buf, SP_PATH_MAX, &len)) return SP_OK; + *path = sp_fs_normalize_path(mem, sp_str(buf, (u32)len)); + return SP_OK; } sp_err_t sp_io_read_file_at(sp_mem_t mem, sp_path_t path, sp_str_t* content) { diff --git a/sp/sp_test.h b/sp/sp_test.h index e29a2423..2da3c96a 100644 --- a/sp/sp_test.h +++ b/sp/sp_test.h @@ -1097,8 +1097,8 @@ static sp_path_t sp_test_golden_root(sp_test_t* t, sp_str_t file) { if (!sp_str_empty(root.sub)) return root; sp_path_t anchors [2] = { sp_path_at_cwd(sp_str_lit(".")) }; - sp_str_t exe = sp_fs_get_exe_path(t->mem); - if (!sp_str_empty(exe)) anchors[1] = sp_path_resolve(sp_fs_parent_path(exe)); + sp_str_t exe = sp_zero; + if (!sp_fs_get_exe_path(t->mem, &exe)) anchors[1] = sp_path_resolve(sp_fs_parent_path(exe)); sp_carr_for(anchors, it) { if (sp_str_empty(anchors[it].sub)) continue; @@ -2123,8 +2123,8 @@ s32 sp_test_main(s32 argc, const c8** argv, const sp_test_entry_t* entries) { sp_tm_epoch_t now = sp_tm_now_epoch(); sp_tm_epoch_t grace = { .s = now.s - SP_TEST_RUNS_GRACE_S, .ns = now.ns }; - sp_str_t exe = sp_fs_get_exe_path(runner->mem); - if (sp_str_empty(exe)) exe = sp_cstr_as_str(argv[0]); + sp_str_t exe = sp_zero; + if (sp_fs_get_exe_path(runner->mem, &exe)) exe = sp_cstr_as_str(argv[0]); sp_path_t runs_dir = sp_path_join(runner->mem, root, sp_fs_get_stem(exe)); runner->dir_root = sp_path_join(runner->mem, runs_dir, sp_test_run_name(runner->mem, now)); diff --git a/test/bench/ubench.h b/test/bench/ubench.h index da4083fa..88a13a77 100644 --- a/test/bench/ubench.h +++ b/test/bench/ubench.h @@ -1488,7 +1488,8 @@ s32 ubench_main(s32 argc, const c8 *const argv[]) { returns, which is what binds the strings into SQLite). */ { sp_mem_arena_marker_t s = sp_mem_begin_scratch(); - sp_str_t exe = sp_fs_get_exe_path(s.mem); + sp_str_t exe = sp_zero; + sp_fs_get_exe_path(s.mem, &exe); sp_cstr_copy_to_n(exe.data, exe.len, exe_path_buf, sizeof(exe_path_buf)); if (exe_path_buf[0]) { diff --git a/test/cli/usage.c b/test/cli/usage.c index 1ff0f10c..8b8bbb5b 100644 --- a/test/cli/usage.c +++ b/test/cli/usage.c @@ -19,7 +19,9 @@ static sp_str_t cli_usage_golden_dir(sp_mem_t mem) { sp_str_t baked = sp_str_lit(SP_CLI_TEST_DIR); if (sp_fs_exists(baked)) return sp_fs_join_path(mem, baked, sp_str_lit("golden")); - sp_str_t dir = sp_fs_parent_path(sp_fs_get_exe_path(mem)); + sp_str_t exe = sp_zero; + sp_fs_get_exe_path(mem, &exe); + sp_str_t dir = sp_fs_parent_path(exe); while (!sp_str_empty(dir)) { sp_str_t candidate = sp_fs_join_path(mem, dir, sp_str_lit("test/cli/golden")); if (sp_fs_exists(candidate)) return candidate; @@ -49,7 +51,9 @@ static void run_cli_usage_test(s32* utest_result, sp_mem_t mem, cli_usage_test_t sp_str_t dir = cli_usage_golden_dir(mem); if (sp_str_empty(dir)) { - SP_TEST_REPORT("could not locate the golden directory from {}\n", sp_fmt_str(sp_fs_get_exe_path(mem))); + sp_str_t exe = sp_zero; + sp_fs_get_exe_path(mem, &exe); + SP_TEST_REPORT("could not locate the golden directory from {}\n", sp_fmt_str(exe)); SP_FAIL(); return; } diff --git a/test/fs/canonicalize.c b/test/fs/canonicalize.c index 36ce5ef7..77463863 100644 --- a/test/fs/canonicalize.c +++ b/test/fs/canonicalize.c @@ -1,7 +1,7 @@ #include "fs.h" typedef struct { - bool empty; + sp_err_t err; bool no_trailing_slash; bool no_backslash; const c8* name; @@ -82,24 +82,24 @@ static const test_t tests [] = { }, }, { - .name = "nonexistent_returns_empty", + .name = "nonexistent_is_not_found", .input = "/this/path/does/not/exist/at/all", .expect = { - .empty = true, + .err = SP_ERR_SYS_NOT_FOUND, }, }, { .name = "empty_input", .input = "", .expect = { - .empty = true, + .err = SP_ERR_SYS_NOT_FOUND, }, }, { .name = "nonexistent_with_dotdot", .input = "no_such_dir/../also_missing.txt", .expect = { - .empty = true, + .err = SP_ERR_SYS_NOT_FOUND, }, }, { @@ -168,11 +168,11 @@ sp_test_each(fs, canonicalize, test_t, tests) { sp_must_ok(t, sp_fs_open_dir_at(sandbox, &dir)); sp_path_t input = sp_path_at(dir, sp_str_view(it->input)); - sp_str_t result = sp_fs_canonicalize_path_at(mem, input); + sp_str_t result = sp_zero; + sp_expect_err_eq(t, sp_fs_canonicalize_path_at(mem, input, &result), it->expect.err); - if (it->expect.empty) { + if (it->expect.err) { sp_sys_close(dir); - if (result.len != 0) sp_test_fail(t, "expected empty, got {}", sp_fmt_str(result)); return SP_OK; } @@ -190,12 +190,14 @@ sp_test_each(fs, canonicalize, test_t, tests) { sp_expect(t, sp_fs_exists_at(sp_path_at(dir, result))); } if (it->expect.idempotent) { - sp_path_t again = sp_path_at(dir, result); - sp_expect_str_eq(t, sp_fs_canonicalize_path_at(mem, again), result); + sp_str_t again = sp_zero; + sp_expect_ok(t, sp_fs_canonicalize_path_at(mem, sp_path_at(dir, result), &again)); + sp_expect_str_eq(t, again, result); } if (it->expect.same_as) { - sp_path_t other = sp_path_at(dir, sp_str_view(it->expect.same_as)); - sp_expect_str_eq(t, result, sp_fs_canonicalize_path_at(mem, other)); + sp_str_t other = sp_zero; + sp_expect_ok(t, sp_fs_canonicalize_path_at(mem, sp_path_at(dir, sp_str_view(it->expect.same_as)), &other)); + sp_expect_str_eq(t, result, other); } sp_sys_close(dir); return SP_OK; @@ -205,6 +207,10 @@ sp_test(fs, canon_dot_resolves_to_cwd) { sp_test_skip_on_wasm() sp_mem_t mem = sp_test_arena(t); - sp_expect_str_eq(t, sp_fs_canonicalize_path(mem, sp_str_lit(".")), sp_fs_get_cwd_path(mem)); + sp_str_t canonical = sp_zero; + sp_str_t cwd = sp_zero; + sp_must_ok(t, sp_fs_canonicalize_path(mem, sp_str_lit("."), &canonical)); + sp_must_ok(t, sp_fs_get_cwd_path(mem, &cwd)); + sp_expect_str_eq(t, canonical, cwd); return SP_OK; } diff --git a/test/fs/fd_relative.c b/test/fs/fd_relative.c index 539ccf3b..66be8270 100644 --- a/test/fs/fd_relative.c +++ b/test/fs/fd_relative.c @@ -128,9 +128,8 @@ sp_test_each(fs, fd_relative, test_t, tests) { } case OP_OPEN: case OP_OPEN_ABS: { - sp_str_t path = it->op == OP_OPEN_ABS - ? sp_fs_canonicalize_path_at(mem, sp_path_join(mem, sandbox, sp_str_view(it->path))) - : sp_str_view(it->path); + sp_str_t path = sp_str_view(it->path); + if (it->op == OP_OPEN_ABS) sp_must_ok(t, sp_fs_canonicalize_path_at(mem, sp_path_join(mem, sandbox, path), &path)); sp_sys_fd_t fd = SP_SYS_INVALID_FD; sp_expect_ok(t, sp_sys_open_s(cwd, path, SP_SYS_OPEN_MODE_RO, 0, &fd)); diff --git a/test/fs/fs.h b/test/fs/fs.h index 7d879947..15b79d0f 100644 --- a/test/fs/fs.h +++ b/test/fs/fs.h @@ -125,8 +125,10 @@ static void fs_apply_setup(sp_test_t* t, sp_path_t sandbox, const fs_setup_t set } case FS_SETUP_FIFO: { #if defined(SP_POSIX) - sp_str_t fifo = sp_fs_join_path(mem, sp_fs_canonicalize_path_at(mem, parent), sp_fs_get_name(path.sub)); - err = mkfifo(sp_cstr_from_str(mem, fifo), 0644) ? SP_ERR_SYS : SP_OK; + sp_str_t dir = sp_zero; + err = sp_fs_canonicalize_path_at(mem, parent, &dir); + sp_str_t fifo = sp_fs_join_path(mem, dir, sp_fs_get_name(path.sub)); + if (!err) err = mkfifo(sp_cstr_from_str(mem, fifo), 0644) ? SP_ERR_SYS : SP_OK; #else err = SP_ERR_SYS_UNSUPPORTED; #endif diff --git a/test/fs/get_cwd.c b/test/fs/get_cwd.c index 8529dbf9..06794f03 100644 --- a/test/fs/get_cwd.c +++ b/test/fs/get_cwd.c @@ -4,7 +4,8 @@ sp_test(fs, get_cwd_contract) { sp_test_skip_on_wasm() - sp_str_t cwd = sp_fs_get_cwd_path(sp_test_arena(t)); + sp_str_t cwd = sp_zero; + sp_must_ok(t, sp_fs_get_cwd_path(sp_test_arena(t), &cwd)); sp_must(t, sp_fs_is_dir(cwd)); sp_must(t, sp_fs_is_absolute(cwd)); return SP_OK; @@ -16,26 +17,29 @@ sp_test(fs, get_cwd_unlinked_cwd_has_no_path, .serial = true) { return sp_test_skip(t, "unlinked-cwd is a Linux-only scenario"); #else sp_mem_t mem = sp_test_arena(t); - sp_str_t original = sp_fs_get_cwd_path(mem); - sp_must_gt(t, original.len, 0); + sp_str_t original = sp_zero; + sp_must_ok(t, sp_fs_get_cwd_path(mem, &original)); sp_path_t sandbox = sp_test_dir(t); sp_sys_fd_t dir = SP_SYS_INVALID_FD; sp_must_ok(t, sp_fs_open_dir_at(sandbox, &dir)); sp_path_t doomed = sp_path_at(dir, sp_str_lit("A")); + sp_str_t canonical = sp_zero; sp_err_t err = sp_fs_create_dir_at(doomed); - if (!err) err = sp_sys_chdir_s(sp_fs_canonicalize_path_at(mem, doomed)); + if (!err) err = sp_fs_canonicalize_path_at(mem, doomed, &canonical); + if (!err) err = sp_sys_chdir_s(canonical); if (!err) err = sp_fs_remove_dir_at(doomed); - sp_str_t cwd = sp_fs_get_cwd_path(mem); + sp_str_t cwd = sp_zero; + sp_err_t unlinked = sp_fs_get_cwd_path(mem, &cwd); // restore cwd before any assertion can fail, so other tests aren't poisoned sp_sys_chdir_s(original); sp_sys_close(dir); sp_must_ok(t, err); - sp_must(t, sp_str_empty(cwd)); + sp_expect_err_eq(t, unlinked, SP_ERR_SYS_NOT_FOUND); return SP_OK; #endif } diff --git a/test/fs/get_exe_path.c b/test/fs/get_exe_path.c index 9261f02b..ea59a39d 100644 --- a/test/fs/get_exe_path.c +++ b/test/fs/get_exe_path.c @@ -4,7 +4,8 @@ sp_test(fs, get_exe_path_basic_properties) { sp_test_skip_on_wasm() - sp_str_t exe = sp_fs_get_exe_path(sp_test_arena(t)); + sp_str_t exe = sp_zero; + sp_must_ok(t, sp_fs_get_exe_path(sp_test_arena(t), &exe)); sp_must_gt(t, exe.len, 0); // normalized: no backslashes, no trailing slash @@ -19,7 +20,8 @@ sp_test(fs, get_exe_path_basic_properties) { sp_test(fs, get_exe_path_is_absolute) { sp_test_skip_on_wasm() - sp_str_t exe = sp_fs_get_exe_path(sp_test_arena(t)); + sp_str_t exe = sp_zero; + sp_must_ok(t, sp_fs_get_exe_path(sp_test_arena(t), &exe)); // absolute: starts with / on POSIX, or X: on Windows sp_must(t, (exe.data[0] == '/') || (exe.len >= 2 && exe.data[1] == ':')); return SP_OK; @@ -28,7 +30,9 @@ sp_test(fs, get_exe_path_is_absolute) { sp_test(fs, get_exe_path_exists_on_disk) { sp_test_skip_on_wasm() - sp_must(t, sp_fs_exists(sp_fs_get_exe_path(sp_test_arena(t)))); + sp_str_t exe = sp_zero; + sp_must_ok(t, sp_fs_get_exe_path(sp_test_arena(t), &exe)); + sp_must(t, sp_fs_exists(exe)); return SP_OK; } @@ -36,14 +40,19 @@ sp_test(fs, get_exe_path_is_canonical) { sp_test_skip_on_wasm() sp_mem_t mem = sp_test_arena(t); - sp_str_t exe = sp_fs_get_exe_path(mem); - sp_expect_str_eq(t, sp_fs_canonicalize_path(mem, exe), exe); + sp_str_t exe = sp_zero; + sp_str_t canonical = sp_zero; + sp_must_ok(t, sp_fs_get_exe_path(mem, &exe)); + sp_must_ok(t, sp_fs_canonicalize_path(mem, exe, &canonical)); + sp_expect_str_eq(t, canonical, exe); return SP_OK; } sp_test(fs, get_exe_path_no_dotdot) { sp_test_skip_on_wasm() - sp_must(t, !sp_str_contains(sp_fs_get_exe_path(sp_test_arena(t)), sp_str_lit(".."))); + sp_str_t exe = sp_zero; + sp_must_ok(t, sp_fs_get_exe_path(sp_test_arena(t), &exe)); + sp_must(t, !sp_str_contains(exe, sp_str_lit(".."))); return SP_OK; } diff --git a/test/fs/get_path_sim.c b/test/fs/get_path_sim.c new file mode 100644 index 00000000..d652e15f --- /dev/null +++ b/test/fs/get_path_sim.c @@ -0,0 +1,62 @@ +#include "sp.h" +#include "sp/sp_test.h" + +typedef enum { + PATH_CWD, + PATH_EXE, + PATH_STORAGE, + PATH_CONFIG, +} path_fn_t; + +typedef struct { + const c8* name; + path_fn_t fn; + sp_err_t err; +} test_t; + +static const test_t tests [] = { + { .name = "cwd", .fn = PATH_CWD, .err = SP_ERR_SYS_NOT_FOUND }, + { .name = "exe", .fn = PATH_EXE, .err = SP_ERR_SYS_UNSUPPORTED }, + { .name = "storage", .fn = PATH_STORAGE, .err = SP_ERR_SYS_NOT_FOUND }, + { .name = "config", .fn = PATH_CONFIG, .err = SP_ERR_SYS_NAME_TOO_LONG }, +}; + +static const test_t* active; + +static sp_err_t get_path(c8* buf, u64 size, u64* len) { + *len = 0; + return active->err; +} + +static sp_err_t get_fd_path(sp_sys_fd_t fd, c8* buf, u64 size, u64* len) { + *len = 0; + return active->err; +} + +static sp_err_t run(sp_test_t* t, test_t* c) { + sp_sys_vtable_t vt = sp_sys_vtable_platform; + vt.get_exe_path = get_path; + vt.get_storage_path = get_path; + vt.get_config_path = get_path; + vt.get_fd_path = get_fd_path; + + sp_mem_t mem = sp_test_arena(t); + sp_str_t path = sp_zero; + sp_err_t err = SP_OK; + + active = c; + const sp_sys_vtable_t* saved = sp_sys_set_vtable(&vt); + switch (c->fn) { + case PATH_CWD: err = sp_fs_get_cwd_path(mem, &path); break; + case PATH_EXE: err = sp_fs_get_exe_path(mem, &path); break; + case PATH_STORAGE: err = sp_fs_get_storage_path(mem, &path); break; + case PATH_CONFIG: err = sp_fs_get_config_path(mem, &path); break; + } + sp_sys_set_vtable(saved); + active = SP_NULLPTR; + + sp_expect_err_eq(t, err, c->err); + return SP_OK; +} + +sp_test_each_fn(fs, get_path_sim, test_t, tests, run, .serial = true); diff --git a/test/fs/path_parent.c b/test/fs/path_parent.c index 4a0d98e3..2e15c5d3 100644 --- a/test/fs/path_parent.c +++ b/test/fs/path_parent.c @@ -15,7 +15,7 @@ static const test_t tests [] = { { .name = "trailing_sep", .sub = "A/B/", .expect = "A" }, { .name = "root_label", .sub = ".", .expect = "./.." }, { .name = "trailing_dot", .sub = "./", .expect = "./.." }, - { .name = "empty", .sub = "", .expect = ".." }, + { .name = "empty", .sub = "", .expect = "" }, { .name = "dotdot", .sub = "..", .expect = "../.." }, { .name = "nested_dotdot", .sub = "A/..", .expect = "A/../.." }, { .name = "trailing_dotdot", .sub = "A/../", .expect = "A/../.." }, diff --git a/test/fs/remove/os.c b/test/fs/remove/os.c index 0e3d387f..c0e55740 100644 --- a/test/fs/remove/os.c +++ b/test/fs/remove/os.c @@ -380,4 +380,21 @@ sp_test(fs, remove_dir_unwritable_subdir_fails) { }); return SP_OK; } + +sp_test(fs, remove_dir_unreadable_empty_subdir_is_removed) { + sp_mem_t mem = sp_test_arena(t); + sp_path_t sandbox = sp_test_dir(t); + sp_path_t locked = sp_path_join(mem, sandbox, sp_str_lit("A/B")); + sp_must_ok(t, sp_fs_create_dir_at(locked)); + sp_must_ok(t, sp_sys_set_file_perms_s(locked.dir, locked.sub, (sp_sys_file_perms_t) { .value = 0 })); + + sp_err_t result = sp_fs_remove_dir_at(sp_path_join(mem, sandbox, sp_str_lit("A"))); + + sp_sys_set_file_perms_s(locked.dir, locked.sub, (sp_sys_file_perms_t) { .value = 0755 }); + sp_expect_ok(t, result); + fs_expect_paths(t, sandbox, (const fs_expected_path_t [FS_MAX_PATHS]) { + { .path = "A" }, + }); + return SP_OK; +} #endif diff --git a/test/fs/remove/sim.c b/test/fs/remove/sim.c index 1b2d654f..47d62842 100644 --- a/test/fs/remove/sim.c +++ b/test/fs/remove/sim.c @@ -243,6 +243,41 @@ static const test_t tests [] = { .rmdirs = 1, }, }, + { + .name = "unreadable_empty_subdir_is_removed", + .dirs = { + { .path = "T", .entries = { { "B", SP_FS_KIND_DIR } } }, + { .path = "T/B", .open = SP_ERR_SYS_ACCESS_DENIED }, + }, + .expect = { + .removed = { { "T/B", SIM_OP_RMDIR }, { "T", SIM_OP_RMDIR } }, + .opens = 2, + .rmdirs = 2, + }, + }, + { + .name = "unreadable_empty_root_is_removed", + .dirs = { + { .path = "T", .open = SP_ERR_SYS_ACCESS_DENIED }, + }, + .expect = { + .removed = { { "T", SIM_OP_RMDIR } }, + .opens = 1, + .rmdirs = 1, + }, + }, + { + .name = "unreadable_subdir_with_entries_is_access_denied", + .dirs = { + { .path = "T", .entries = { { "B", SP_FS_KIND_DIR } } }, + { .path = "T/B", .open = SP_ERR_SYS_ACCESS_DENIED, .entries = { { "C", SP_FS_KIND_FILE } } }, + }, + .expect = { + .err = SP_ERR_SYS_ACCESS_DENIED, + .opens = 2, + .rmdirs = 1, + }, + }, { .name = "read_error_fails_fast_and_unwinds", .dirs = { diff --git a/test/fs/system_paths.c b/test/fs/system_paths.c index 1a7a8967..0ec2609e 100644 --- a/test/fs/system_paths.c +++ b/test/fs/system_paths.c @@ -14,21 +14,29 @@ sp_test(fs, system_paths_nonempty) { sp_test_skip_on_wasm() sp_mem_t mem = sp_test_arena(t); - sp_must_gt(t, sp_fs_get_storage_path(mem).len, 0); - sp_must_gt(t, sp_fs_get_config_path(mem).len, 0); + sp_str_t storage = sp_zero; + sp_str_t config = sp_zero; + sp_must_ok(t, sp_fs_get_storage_path(mem, &storage)); + sp_must_ok(t, sp_fs_get_config_path(mem, &config)); + sp_must_gt(t, storage.len, 0); + sp_must_gt(t, config.len, 0); return SP_OK; } sp_test(fs, system_paths_storage_path_normalized) { sp_test_skip_on_wasm() - expect_normalized(t, sp_fs_get_storage_path(sp_test_arena(t)), "storage_path"); + sp_str_t path = sp_zero; + sp_must_ok(t, sp_fs_get_storage_path(sp_test_arena(t), &path)); + expect_normalized(t, path, "storage_path"); return SP_OK; } sp_test(fs, system_paths_config_path_normalized) { sp_test_skip_on_wasm() - expect_normalized(t, sp_fs_get_config_path(sp_test_arena(t)), "config_path"); + sp_str_t path = sp_zero; + sp_must_ok(t, sp_fs_get_config_path(sp_test_arena(t), &path)); + expect_normalized(t, path, "config_path"); return SP_OK; } diff --git a/test/gdb.c b/test/gdb.c index 09e4fbb8..92ee5bd2 100644 --- a/test/gdb.c +++ b/test/gdb.c @@ -41,8 +41,9 @@ typedef struct gdb { } sp_gdb; static sp_str_t gdb_fixture_path(sp_mem_t mem) { - sp_str_t exe = sp_fs_parent_path(sp_fs_get_exe_path(mem)); - sp_str_t fixture = sp_fs_join_path(mem, exe, sp_str_lit("gdb_fixture")); + sp_str_t exe = sp_zero; + sp_fs_get_exe_path(mem, &exe); + sp_str_t fixture = sp_fs_join_path(mem, sp_fs_parent_path(exe), sp_str_lit("gdb_fixture")); return sp_fs_replace_ext(mem, fixture, sp_os_get_executable_ext()); } diff --git a/test/leak.c b/test/leak.c index 06bfc8ec..f8c02e0a 100644 --- a/test/leak.c +++ b/test/leak.c @@ -166,8 +166,9 @@ UTEST(tracking, realloc_zero_is_free) { // SP_PS LEAK // ////////////// static sp_str_t leak_ps_get_process_path(sp_mem_t mem) { - sp_str_t exe = sp_fs_parent_path(sp_fs_get_exe_path(mem)); - sp_str_t process = sp_fs_join_path(mem, exe, sp_str_lit("process")); + sp_str_t exe = sp_zero; + sp_fs_get_exe_path(mem, &exe); + sp_str_t process = sp_fs_join_path(mem, sp_fs_parent_path(exe), sp_str_lit("process")); return sp_fs_replace_ext(mem, process, sp_os_get_executable_ext()); } diff --git a/test/ps.c b/test/ps.c index 76dec2e9..10eb6627 100644 --- a/test/ps.c +++ b/test/ps.c @@ -53,15 +53,17 @@ typedef struct { // FIXTURES // ////////////// sp_str_t get_process_path(sp_mem_t mem) { - sp_str_t exe = sp_fs_parent_path(sp_fs_get_exe_path(mem)); - sp_str_t process = sp_fs_join_path(mem, exe, sp_str_lit("process")); + sp_str_t exe = sp_zero; + sp_fs_get_exe_path(mem, &exe); + sp_str_t process = sp_fs_join_path(mem, sp_fs_parent_path(exe), sp_str_lit("process")); process = sp_fs_replace_ext(mem, process, sp_os_get_executable_ext()); return process; } const c8* get_process_path_c(sp_mem_t mem) { - sp_str_t exe = sp_fs_parent_path(sp_fs_get_exe_path(mem)); - sp_str_t process = sp_fs_join_path(mem, exe, sp_str_lit("process")); + sp_str_t exe = sp_zero; + sp_fs_get_exe_path(mem, &exe); + sp_str_t process = sp_fs_join_path(mem, sp_fs_parent_path(exe), sp_str_lit("process")); process = sp_fs_replace_ext(mem, process, sp_os_get_executable_ext()); return sp_str_to_cstr(mem, process); } diff --git a/test/sys/inherit.c b/test/sys/inherit.c index abcaf0c9..e1acdbd1 100644 --- a/test/sys/inherit.c +++ b/test/sys/inherit.c @@ -42,8 +42,10 @@ static const c8* inherited_name(s64 value) { static s32 spawn_probe(sp_test_t* t, const c8* op, s64 handle) { sp_mem_t mem = sp_test_arena(t); + sp_str_t exe = sp_zero; + sp_expect_ok(t, sp_fs_get_exe_path(mem, &exe)); sp_ps_output_t out = sp_ps_run(mem, (sp_ps_config_t) { - .command = sp_fs_get_exe_path(mem), + .command = exe, .args = { sp_str_lit("probe"), sp_cstr_as_str(op), diff --git a/test/sys/path.c b/test/sys/path.c index afde5401..46218a48 100644 --- a/test/sys/path.c +++ b/test/sys/path.c @@ -393,7 +393,11 @@ static sp_err_t run(sp_test_t* t, test_t* c) { sp_str_t path = sp_cstr_as_str(c->path); if (c->path_kind == PATH_LONG) path = long_path(mem, c->path); - if (c->path_kind == PATH_ABS) path = abs_path(mem, sp_fs_canonicalize_path_at(mem, sandbox), c->path); + if (c->path_kind == PATH_ABS) { + sp_str_t root = sp_zero; + sp_must_ok(t, sp_fs_canonicalize_path_at(mem, sandbox, &root)); + path = abs_path(mem, root, c->path); + } sp_sys_file_meta_t meta = sp_zero; sp_err_t err = SP_OK; diff --git a/test/sys/readlink.c b/test/sys/readlink.c index a966295a..341ea918 100644 --- a/test/sys/readlink.c +++ b/test/sys/readlink.c @@ -53,7 +53,9 @@ static sp_err_t run(sp_test_t* t, test_t* c) { case SETUP_LINK_ABSOLUTE: { sp_test_skip_without_absolute_names(); sp_test_skip_without_symlinks(); - expected = sp_fs_join_path(mem, sp_fs_canonicalize_path_at(mem, sandbox), sp_cstr_as_str(c->target)); + sp_str_t root = sp_zero; + sp_must_ok(t, sp_fs_canonicalize_path_at(mem, sandbox, &root)); + expected = sp_fs_join_path(mem, root, sp_cstr_as_str(c->target)); sp_must_ok(t, sp_fs_create_sym_link_at(expected, path, SP_FS_KIND_FILE)); break; } diff --git a/test/test/dir.c b/test/test/dir.c index 86d9ed67..49ac2d7b 100644 --- a/test/test/dir.c +++ b/test/test/dir.c @@ -36,7 +36,8 @@ sp_test_each(runner, dir, dir_case_t, dir_cases) { sp_mem_t mem = sp_test_arena(t); sp_path_t sandbox = sp_test_dir(t); - sp_str_t exe = sp_fs_get_exe_path(mem); + sp_str_t exe = sp_zero; + sp_must_ok(t, sp_fs_get_exe_path(mem, &exe)); sp_ps_config_t config = { .command = exe, diff --git a/test/test/golden.c b/test/test/golden.c index 28d30dea..74e40813 100644 --- a/test/test/golden.c +++ b/test/test/golden.c @@ -92,14 +92,16 @@ sp_test_each(runner, golden, test_t, tests) { sp_mem_t mem = sp_test_arena(t); sp_path_t sandbox = sp_test_dir(t); - sp_str_t exe = sp_fs_get_exe_path(mem); + sp_str_t exe = sp_zero; + sp_must_ok(t, sp_fs_get_exe_path(mem, &exe)); sp_path_t src = sp_path_join(mem, sandbox, sp_str_lit("S/main.c")); sp_path_t golden = sp_path_join(mem, sandbox, sp_str_lit("S/G")); sp_path_t actual = sp_path_join(mem, sandbox, sp_str_lit("S/G.actual")); sp_must_ok(t, sp_fs_create_dir_at(sp_path_parent(mem, src))); sp_must_ok(t, sp_fs_create_file_at(src)); - sp_str_t file = it->absolute ? sp_fs_canonicalize_path_at(mem, src) : sp_str_lit("S/main.c"); + sp_str_t file = sp_str_lit("S/main.c"); + if (it->absolute) sp_must_ok(t, sp_fs_canonicalize_path_at(mem, src, &file)); if (it->golden) sp_must_ok(t, sp_fs_create_file_cstr_at(golden, it->golden)); if (it->update) sp_must_ok(t, sp_fs_create_file_cstr_at(actual, "stale")); diff --git a/test/test/keep.c b/test/test/keep.c index 9c3e0655..9f77ce7a 100644 --- a/test/test/keep.c +++ b/test/test/keep.c @@ -116,7 +116,8 @@ sp_test_each(runner, keep, keep_case_t, keep_cases) { sp_mem_t mem = sp_test_arena(t); sp_path_t sandbox = sp_test_dir(t); - sp_str_t exe = sp_fs_get_exe_path(mem); + sp_str_t exe = sp_zero; + sp_must_ok(t, sp_fs_get_exe_path(mem, &exe)); sp_ps_config_t config = { .command = exe, diff --git a/test/test/prune.c b/test/test/prune.c index 748d3cf7..78e1c96d 100644 --- a/test/test/prune.c +++ b/test/test/prune.c @@ -70,7 +70,8 @@ sp_test_each(runner, prune, prune_case_t, prune_cases) { sp_mem_t mem = sp_test_arena(t); sp_path_t sandbox = sp_test_dir(t); - sp_str_t exe = sp_fs_get_exe_path(mem); + sp_str_t exe = sp_zero; + sp_must_ok(t, sp_fs_get_exe_path(mem, &exe)); sp_path_t runs = sp_path_join(mem, sandbox, sp_fs_join_path(mem, sp_str_lit("T"), sp_fs_get_stem(exe))); sp_tm_epoch_t now = sp_tm_now_epoch(); diff --git a/test/tools/test.h b/test/tools/test.h index 23f46e7b..5bfaa964 100644 --- a/test/tools/test.h +++ b/test/tools/test.h @@ -162,7 +162,9 @@ static sp_str_t sp_test_file_manager_get_top_level(sp_mem_t a) { return sp_test_file_manager_top_level; } - sp_str_t tmp = sp_fs_join_path(a, sp_fs_get_cwd_path(a), sp_str_lit(".tmp")); + sp_str_t cwd = sp_zero; + sp_fs_get_cwd_path(a, &cwd); + sp_str_t tmp = sp_fs_join_path(a, cwd, sp_str_lit(".tmp")); if (!sp_fs_exists(tmp)) { sp_fs_create_dir(tmp); } @@ -178,14 +180,14 @@ static sp_str_t sp_test_file_manager_get_top_level(sp_mem_t a) { // Cache in a long-lived allocator (os) so the result outlives `a`, which is // typically a per-test arena that gets destroyed in cleanup. - sp_test_file_manager_top_level = sp_fs_canonicalize_path(sp_mem_os_new(), root); + sp_fs_canonicalize_path(sp_mem_os_new(), root, &sp_test_file_manager_top_level); return sp_test_file_manager_top_level; } void sp_test_file_manager_init(sp_test_file_manager_t* fs) { fs->arena = sp_mem_arena_new(sp_mem_os_new()); fs->mem = sp_mem_arena_as_allocator(fs->arena); - fs->paths.bin = sp_fs_get_exe_path(fs->mem); + sp_fs_get_exe_path(fs->mem, &fs->paths.bin); fs->paths.test = sp_test_file_manager_get_top_level(fs->mem); if (!sp_fs_exists(fs->paths.test)) { diff --git a/tools/sp.c b/tools/sp.c index e61fa16b..88e62308 100644 --- a/tools/sp.c +++ b/tools/sp.c @@ -383,7 +383,9 @@ static test_t test_init(sp_mem_t mem, tool_t* tool, const target_t* target, sp_s break; } case EXEC_DOCKER: { - sp_str_t mount = sp_fmt(mem, "{}:/sp", sp_fmt_str(sp_fs_get_cwd_path(mem))).value; + sp_str_t cwd = sp_zero; + sp_fs_get_cwd_path(mem, &cwd); + sp_str_t mount = sp_fmt(mem, "{}:/sp", sp_fmt_str(cwd)).value; sp_str_t path = sp_fmt(mem, "/sp/{}", sp_fmt_str(rel)).value; test.display = sp_fmt(mem, "docker run --rm --platform linux/arm64 -v {} -w /sp debian:stable-slim {}", sp_fmt_str(mount), sp_fmt_str(path)).value; test.config = (sp_ps_config_t) { From 33a5d0b139325e9c1fed42160d30518dc46a6081 Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Thu, 1 Oct 2026 03:33:56 -0400 Subject: [PATCH 67/72] fs: remove_dir rmdirs directories it cannot list --- sp.h | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/sp.h b/sp.h index b387dfcc..e18ec08c 100644 --- a/sp.h +++ b/sp.h @@ -20848,11 +20848,22 @@ sp_err_t sp_fs_remove_dir(sp_str_t path) { Once */ -SP_PRIVATE sp_err_t sp_fs_remove_pass(sp_path_t path, bool* progressed) { +SP_PRIVATE sp_err_t sp_fs_remove_unlisted(sp_path_t path) { + sp_err_t err = sp_sys_rmdir_s(path.dir, path.sub); + return err == SP_ERR_SYS_NOT_EMPTY ? SP_ERR_SYS_ACCESS_DENIED : err; +} + +SP_PRIVATE sp_err_t sp_fs_remove_enter(sp_fs_it_t* it) { + sp_err_t err = sp_fs_it_enter(it); + return err == SP_ERR_SYS_ACCESS_DENIED ? sp_fs_remove_unlisted(it->at) : err; +} + +SP_PRIVATE sp_err_t sp_fs_remove_pass(sp_path_t path, bool* progressed, bool* listed) { sp_mem_arena_marker_t s = sp_mem_begin_scratch(); sp_fs_it_t it = sp_fs_it_new_at(s.mem, path, SP_SYS_OPEN_DIR_NO_FOLLOW); bool progress [SP_FS_IT_MAX_DEPTH] = sp_zero; sp_err_t err = it.err; + *listed = err != SP_ERR_SYS_ACCESS_DENIED; while (!err && sp_fs_it_next(&it)) { u64 depth = sp_da_size(it.stack); @@ -20866,12 +20877,12 @@ SP_PRIVATE sp_err_t sp_fs_remove_pass(sp_path_t path, bool* progressed) { // entry kinds are hints; flip on a contradicting error so a lying or // absent d_type can't strand an entry if (it.entry.kind == SP_FS_KIND_DIR) { - err = sp_fs_it_enter(&it); + err = sp_fs_remove_enter(&it); if (err == SP_ERR_SYS_NOT_DIR || err == SP_ERR_SYS_LOOP) err = sp_sys_unlink_s(it.at.dir, it.at.sub); } else { err = sp_sys_unlink_s(it.at.dir, it.at.sub); - if (err == SP_ERR_SYS_IS_DIR) err = sp_fs_it_enter(&it); + if (err == SP_ERR_SYS_IS_DIR) err = sp_fs_remove_enter(&it); } break; } @@ -20906,10 +20917,11 @@ sp_err_t sp_fs_remove_dir_at(sp_path_t path) { bool progressed = false; while (true) { bool retry = progressed; - sp_err_t err = sp_fs_remove_pass(path, &progressed); + bool listed = false; + sp_err_t err = sp_fs_remove_pass(path, &progressed, &listed); if (err == SP_ERR_SYS_NOT_FOUND && retry) return SP_OK; - if (err) return err; - err = sp_sys_rmdir_s(path.dir, path.sub); + if (err && listed) return err; + err = listed ? sp_sys_rmdir_s(path.dir, path.sub) : sp_fs_remove_unlisted(path); if (err == SP_ERR_SYS_NOT_EMPTY && progressed) continue; return err == SP_ERR_SYS_NOT_FOUND ? SP_OK : err; } From daa3efe4cc3be9df26f515f657b8c1f5eaf5c7df Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Thu, 1 Oct 2026 03:34:13 -0400 Subject: [PATCH 68/72] fs: path getters return the sys error --- sp.h | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/sp.h b/sp.h index e18ec08c..bedba25f 100644 --- a/sp.h +++ b/sp.h @@ -20446,8 +20446,7 @@ sp_err_t sp_fs_canonicalize_path(sp_mem_t mem, sp_str_t path, sp_str_t* canonica sp_err_t sp_fs_get_exe_path(sp_mem_t mem, sp_str_t* path) { c8 buf[SP_PATH_MAX]; u64 len = 0; - *path = sp_zero_s(sp_str_t); - if (sp_sys_get_exe_path(buf, SP_PATH_MAX, &len)) return SP_OK; + sp_try(sp_sys_get_exe_path(buf, SP_PATH_MAX, &len)); *path = sp_str_copy(mem, sp_str(buf, (u32)len)); return SP_OK; } @@ -20455,8 +20454,7 @@ sp_err_t sp_fs_get_exe_path(sp_mem_t mem, sp_str_t* path) { sp_err_t sp_fs_get_cwd_path(sp_mem_t mem, sp_str_t* path) { c8 buf[SP_PATH_MAX]; u64 len = 0; - *path = sp_zero_s(sp_str_t); - if (sp_sys_get_fd_path(sp_fs_get_cwd(), buf, SP_PATH_MAX, &len)) return SP_OK; + sp_try(sp_sys_get_fd_path(sp_fs_get_cwd(), buf, SP_PATH_MAX, &len)); *path = sp_str_copy(mem, sp_str(buf, (u32)len)); return SP_OK; } @@ -20464,8 +20462,7 @@ sp_err_t sp_fs_get_cwd_path(sp_mem_t mem, sp_str_t* path) { sp_err_t sp_fs_get_storage_path(sp_mem_t mem, sp_str_t* path) { c8 buf[SP_PATH_MAX]; u64 len = 0; - *path = sp_zero_s(sp_str_t); - if (sp_sys_get_storage_path(buf, SP_PATH_MAX, &len)) return SP_OK; + sp_try(sp_sys_get_storage_path(buf, SP_PATH_MAX, &len)); *path = sp_fs_normalize_path(mem, sp_str(buf, (u32)len)); return SP_OK; } @@ -20473,8 +20470,7 @@ sp_err_t sp_fs_get_storage_path(sp_mem_t mem, sp_str_t* path) { sp_err_t sp_fs_get_config_path(sp_mem_t mem, sp_str_t* path) { c8 buf[SP_PATH_MAX]; u64 len = 0; - *path = sp_zero_s(sp_str_t); - if (sp_sys_get_config_path(buf, SP_PATH_MAX, &len)) return SP_OK; + sp_try(sp_sys_get_config_path(buf, SP_PATH_MAX, &len)); *path = sp_fs_normalize_path(mem, sp_str(buf, (u32)len)); return SP_OK; } From 0a63c8bb2e488ac277776477ad8875b655ac03fc Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Thu, 1 Oct 2026 03:34:45 -0400 Subject: [PATCH 69/72] fs: canonicalize_path returns its error --- sp.h | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/sp.h b/sp.h index bedba25f..7bd7f28c 100644 --- a/sp.h +++ b/sp.h @@ -20419,23 +20419,17 @@ sp_tm_epoch_t sp_fs_get_mod_time(sp_str_t path) { return sp_fs_get_mod_time_at(sp_path_at_cwd(path)); } -SP_PRIVATE sp_str_t sp_fs_canonicalize_to(sp_path_t path, c8 buf [SP_PATH_MAX]) { - if (sp_str_empty(path.sub)) return sp_zero_s(sp_str_t); - +sp_err_t sp_fs_canonicalize_path_at(sp_mem_t mem, sp_path_t path, sp_str_t* canonical) { sp_sys_fd_t fd = SP_SYS_INVALID_FD; - if (sp_sys_open_s(path.dir, path.sub, SP_SYS_OPEN_MODE_PATH, 0, &fd)) return sp_zero_s(sp_str_t); + sp_try(sp_sys_open_s(path.dir, path.sub, SP_SYS_OPEN_MODE_PATH, 0, &fd)); + c8 buf [SP_PATH_MAX]; u64 len = 0; sp_err_t err = sp_sys_get_fd_path(fd, buf, SP_PATH_MAX, &len); sp_sys_close(fd); - if (err) return sp_zero_s(sp_str_t); + sp_try(err); - return sp_str(buf, (u32)len); -} - -sp_err_t sp_fs_canonicalize_path_at(sp_mem_t mem, sp_path_t path, sp_str_t* canonical) { - c8 buf [SP_PATH_MAX]; - *canonical = sp_str_copy(mem, sp_fs_canonicalize_to(path, buf)); + *canonical = sp_str_copy(mem, sp_str(buf, (u32)len)); return SP_OK; } From 3dfac3087455bcb7a61ce16c4fdd598f7ad32089 Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Thu, 1 Oct 2026 03:34:55 -0400 Subject: [PATCH 70/72] fs: sp_path_parent leaves an empty sub empty --- sp.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sp.h b/sp.h index 7bd7f28c..4a83dadb 100644 --- a/sp.h +++ b/sp.h @@ -20319,7 +20319,7 @@ sp_path_t sp_path_join(sp_mem_t mem, sp_path_t path, sp_str_t sub) { } sp_path_t sp_path_parent(sp_mem_t mem, sp_path_t path) { - if (sp_str_empty(path.sub) || sp_fs_is_dot(sp_fs_get_name(path.sub))) return sp_path_join(mem, path, sp_str_lit("..")); + if (sp_fs_is_dot(sp_fs_get_name(path.sub))) return sp_path_join(mem, path, sp_str_lit("..")); return sp_path_at(path.dir, sp_fs_parent_path(path.sub)); } From b29936a23e61c9df4de72dc02c427c9d7f5ea648 Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Thu, 1 Oct 2026 03:35:39 -0400 Subject: [PATCH 71/72] sys: storage and config paths leave sys normalized --- sp.h | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/sp.h b/sp.h index 4a83dadb..267adb3f 100644 --- a/sp.h +++ b/sp.h @@ -8913,32 +8913,32 @@ void sp_sys_futex_wake_all_p(u32* addr) { } #if defined(SP_WIN32) -static sp_err_t sp_sys_copy_env_var(const c8* name, c8* buf, u64 size, u64* len) { +static sp_err_t sp_sys_copy_env_path(const c8* name, c8* buf, u64 size, u64* len) { *len = 0; DWORD n = GetEnvironmentVariableA(name, buf, (DWORD)size); if (n == 0) return SP_ERR_SYS_NOT_FOUND; if (n >= size) return SP_ERR_SYS_NAME_TOO_LONG; - *len = n; + *len = (u64)sp_sys_win32_copy_path(sp_fs_trim_path(sp_str(buf, n)), buf, size); return SP_OK; } sp_err_t sp_sys_get_storage_path_p(c8* buf, u64 size, u64* len) { - return sp_sys_copy_env_var("LOCALAPPDATA", buf, size, len); + return sp_sys_copy_env_path("LOCALAPPDATA", buf, size, len); } sp_err_t sp_sys_get_config_path_p(c8* buf, u64 size, u64* len) { - return sp_sys_copy_env_var("APPDATA", buf, size, len); + return sp_sys_copy_env_path("APPDATA", buf, size, len); } #else static sp_err_t sp_sys_xdg_or_home(sp_str_t xdg_var, sp_str_t home_suffix, c8* buf, u64 size, u64* len) { *len = 0; sp_mem_arena_marker_t scratch = sp_mem_begin_scratch(); - sp_str_t value = sp_os_env_get(xdg_var); + sp_str_t value = sp_fs_trim_path(sp_os_env_get(xdg_var)); if (sp_str_empty(value)) { sp_str_t home = sp_os_env_get(sp_str_lit("HOME")); if (!sp_str_empty(home)) { - value = sp_str_join(sp_mem_get_scratch(), home, home_suffix, sp_str_lit("/")); + value = sp_fs_join_path(scratch.mem, home, home_suffix); } } @@ -20457,7 +20457,7 @@ sp_err_t sp_fs_get_storage_path(sp_mem_t mem, sp_str_t* path) { c8 buf[SP_PATH_MAX]; u64 len = 0; sp_try(sp_sys_get_storage_path(buf, SP_PATH_MAX, &len)); - *path = sp_fs_normalize_path(mem, sp_str(buf, (u32)len)); + *path = sp_str_copy(mem, sp_str(buf, (u32)len)); return SP_OK; } @@ -20465,7 +20465,7 @@ sp_err_t sp_fs_get_config_path(sp_mem_t mem, sp_str_t* path) { c8 buf[SP_PATH_MAX]; u64 len = 0; sp_try(sp_sys_get_config_path(buf, SP_PATH_MAX, &len)); - *path = sp_fs_normalize_path(mem, sp_str(buf, (u32)len)); + *path = sp_str_copy(mem, sp_str(buf, (u32)len)); return SP_OK; } From 572214f773fd058b3d49db3c10d563102f8914e8 Mon Sep 17 00:00:00 2001 From: Thomas Spader Date: Thu, 1 Oct 2026 03:36:55 -0400 Subject: [PATCH 72/72] fs: rename the sp_path_t constructors --- example/fs/iterator.c | 2 +- example/fs/ls.c | 2 +- example/fs/preopen.c | 4 +- example/fs/roots.c | 2 +- example/fs/workspace.c | 14 ++-- sp.h | 92 +++++++++++++------------- sp/sp_msvc.h | 4 +- sp/sp_test.h | 16 ++--- test/fs/atomic.c | 4 +- test/fs/canonicalize.c | 8 +-- test/fs/copy.c | 4 +- test/fs/create_dir_sim.c | 2 +- test/fs/dir/os.c | 2 +- test/fs/dir/sim.c | 2 +- test/fs/get_cwd.c | 8 +-- test/fs/it/enter_sim.c | 2 +- test/fs/it/os.c | 2 +- test/fs/it/sim.c | 2 +- test/fs/{resolve.c => path_from_str.c} | 4 +- test/fs/path_join.c | 2 +- test/fs/path_parent.c | 2 +- test/fs/remove/os.c | 8 +-- test/fs/remove/sim.c | 2 +- test/io/file.c | 8 +-- test/sys/fd_path.c | 4 +- test/test/golden.c | 2 +- tools/sp.c | 2 +- 27 files changed, 103 insertions(+), 103 deletions(-) rename test/fs/{resolve.c => path_from_str.c} (97%) diff --git a/example/fs/iterator.c b/example/fs/iterator.c index f577a0a9..e0a4f6d1 100644 --- a/example/fs/iterator.c +++ b/example/fs/iterator.c @@ -30,7 +30,7 @@ s32 run(s32 num_args, const c8** args) { sp_str_t dir = num_args > 1 ? sp_cstr_as_str(args[1]) : sp_str_lit("."); sp_mem_arena_marker_t s = sp_mem_begin_scratch(); - sp_fs_it_t it = sp_fs_it_new_at(s.mem, sp_path_resolve(dir), 0); + sp_fs_it_t it = sp_fs_it_new_at(s.mem, sp_path_from_str(dir), 0); sp_err_t err = it.err; u32 dirs = 0; while (!err && sp_fs_it_next(&it)) { diff --git a/example/fs/ls.c b/example/fs/ls.c index c5d7bc30..87e3f45f 100644 --- a/example/fs/ls.c +++ b/example/fs/ls.c @@ -34,7 +34,7 @@ s32 run(s32 num_args, const c8** args) { sp_str_t dir = num_args > 1 ? sp_cstr_as_str(args[1]) : sp_str_lit("."); sp_mem_arena_marker_t s = sp_mem_begin_scratch(); - sp_fs_it_t it = sp_fs_it_new_at(s.mem, sp_path_resolve(dir), 0); + sp_fs_it_t it = sp_fs_it_new_at(s.mem, sp_path_from_str(dir), 0); while (sp_fs_it_next(&it)) { print_entry(s.mem, &it); } diff --git a/example/fs/preopen.c b/example/fs/preopen.c index 2fd0d259..3b83f9fb 100644 --- a/example/fs/preopen.c +++ b/example/fs/preopen.c @@ -12,8 +12,8 @@ static sp_err_t start(sp_mem_t mem, sp_path_t config, sp_path_t state) { s32 run(s32 num_args, const c8** args) { sp_mem_arena_marker_t s = sp_mem_begin_scratch(); - sp_path_t config = sp_path_resolve(sp_str_lit("/config/app.toml")); - sp_path_t state = sp_path_resolve(sp_str_lit("/data/last_run")); + sp_path_t config = sp_path_from_str(sp_str_lit("/config/app.toml")); + sp_path_t state = sp_path_from_str(sp_str_lit("/data/last_run")); sp_err_t err = start(s.mem, config, state); if (err) { sp_log("{.red}", sp_fmt_str(sp_err_str(err))); diff --git a/example/fs/roots.c b/example/fs/roots.c index 9282cf20..1f0811a4 100644 --- a/example/fs/roots.c +++ b/example/fs/roots.c @@ -14,7 +14,7 @@ static void print_root(s32 it) { } static void print_path(sp_str_t path) { - sp_path_t resolved = sp_path_resolve(path); + sp_path_t resolved = sp_path_from_str(path); sp_log( "{:>4} {} {.gray} {}", sp_fmt_int(resolved.dir), diff --git a/example/fs/workspace.c b/example/fs/workspace.c index e0d79ae9..8ade0591 100644 --- a/example/fs/workspace.c +++ b/example/fs/workspace.c @@ -3,7 +3,7 @@ static sp_err_t list(sp_sys_fd_t dir) { sp_mem_arena_marker_t s = sp_mem_begin_scratch(); - sp_fs_it_t it = sp_fs_it_new_at(s.mem, sp_path_at(dir, sp_str_lit(".")), 0); + sp_fs_it_t it = sp_fs_it_new_at(s.mem, sp_path(dir, sp_str_lit(".")), 0); while (sp_fs_it_walk(&it)) { sp_log("{}", sp_fmt_str(it.entry.rel)); } @@ -15,11 +15,11 @@ static sp_err_t list(sp_sys_fd_t dir) { } static sp_err_t build(sp_sys_fd_t dir) { - sp_try(sp_fs_create_dir_at(sp_path_at(dir, sp_str_lit("src/nested")))); - sp_try(sp_fs_create_file_cstr_at(sp_path_at(dir, sp_str_lit("src/A")), "A")); - sp_try(sp_fs_create_file_cstr_at(sp_path_at(dir, sp_str_lit("src/nested/B")), "B")); - sp_try(sp_fs_copy_at(sp_path_at(dir, sp_str_lit("src")), sp_path_at(dir, sp_str_lit("dst")), SP_FS_ATOMIC_REPLACE)); - sp_try(sp_fs_remove_dir_at(sp_path_at(dir, sp_str_lit("src")))); + sp_try(sp_fs_create_dir_at(sp_path(dir, sp_str_lit("src/nested")))); + sp_try(sp_fs_create_file_cstr_at(sp_path(dir, sp_str_lit("src/A")), "A")); + sp_try(sp_fs_create_file_cstr_at(sp_path(dir, sp_str_lit("src/nested/B")), "B")); + sp_try(sp_fs_copy_at(sp_path(dir, sp_str_lit("src")), sp_path(dir, sp_str_lit("dst")), SP_FS_ATOMIC_REPLACE)); + sp_try(sp_fs_remove_dir_at(sp_path(dir, sp_str_lit("src")))); return list(dir); } @@ -40,7 +40,7 @@ static sp_err_t work(sp_path_t path) { s32 run(s32 num_args, const c8** args) { sp_str_t name = num_args > 1 ? sp_cstr_as_str(args[1]) : sp_str_lit("workspace"); - sp_err_t err = work(sp_path_resolve(name)); + sp_err_t err = work(sp_path_from_str(name)); if (err) { sp_log("{.red}: {}", sp_fmt_str(name), sp_fmt_str(sp_err_str(err))); } diff --git a/sp.h b/sp.h index 267adb3f..4f03e592 100644 --- a/sp.h +++ b/sp.h @@ -3130,9 +3130,9 @@ SP_API sp_err_t sp_fs_get_storage_path(sp_mem_t mem, sp_str_t* path) SP_API sp_err_t sp_fs_get_config_path(sp_mem_t mem, sp_str_t* path); SP_API sp_err_t sp_fs_canonicalize_path(sp_mem_t mem, sp_str_t path, sp_str_t* canonical); SP_API sp_err_t sp_fs_canonicalize_path_at(sp_mem_t mem, sp_path_t path, sp_str_t* canonical); -SP_API sp_path_t sp_path_at(sp_sys_fd_t dir, sp_str_t sub); -SP_API sp_path_t sp_path_at_cwd(sp_str_t sub); -SP_API sp_path_t sp_path_resolve(sp_str_t path); +SP_API sp_path_t sp_path(sp_sys_fd_t dir, sp_str_t sub); +SP_API sp_path_t sp_path_cwd(sp_str_t sub); +SP_API sp_path_t sp_path_from_str(sp_str_t path); SP_API sp_path_t sp_path_join(sp_mem_t mem, sp_path_t path, sp_str_t sub); SP_API sp_path_t sp_path_parent(sp_mem_t mem, sp_path_t path); @@ -17721,7 +17721,7 @@ sp_err_t sp_io_file_reader_from_path_at(sp_io_file_reader_t* r, sp_path_t path) } sp_err_t sp_io_file_reader_from_path(sp_io_file_reader_t* r, sp_str_t path) { - return sp_io_file_reader_from_path_at(r, sp_path_at_cwd(path)); + return sp_io_file_reader_from_path_at(r, sp_path_cwd(path)); } sp_err_t sp_io_file_reader_seek(sp_io_file_reader_t* r, s64 offset, sp_io_whence_t whence, s64* position) { @@ -18398,7 +18398,7 @@ sp_err_t sp_io_file_writer_from_path_at(sp_io_file_writer_t* w, sp_path_t path) } sp_err_t sp_io_file_writer_from_path(sp_io_file_writer_t* w, sp_str_t path) { - return sp_io_file_writer_from_path_at(w, sp_path_at_cwd(path)); + return sp_io_file_writer_from_path_at(w, sp_path_cwd(path)); } sp_err_t sp_io_writer_set_buffer(sp_io_writer_t* writer, u8* ptr, u64 size) { @@ -20272,12 +20272,12 @@ sp_sys_fd_t sp_fs_get_cwd() { return sp_sys_get_root(0); } -sp_path_t sp_path_at(sp_sys_fd_t dir, sp_str_t sub) { +sp_path_t sp_path(sp_sys_fd_t dir, sp_str_t sub) { return (sp_path_t) { .dir = dir, .sub = sub }; } -sp_path_t sp_path_at_cwd(sp_str_t sub) { - return sp_path_at(sp_fs_get_cwd(), sub); +sp_path_t sp_path_cwd(sp_str_t sub) { + return sp_path(sp_fs_get_cwd(), sub); } SP_PRIVATE s32 sp_fs_match_label(sp_str_t path, sp_str_t label) { @@ -20292,10 +20292,10 @@ SP_PRIVATE s32 sp_fs_match_label(sp_str_t path, sp_str_t label) { return (s32)at; } -sp_path_t sp_path_resolve(sp_str_t path) { - if (!sp_fs_is_absolute(path)) return sp_path_at_cwd(path); +sp_path_t sp_path_from_str(sp_str_t path) { + if (!sp_fs_is_absolute(path)) return sp_path_cwd(path); - sp_path_t best = sp_path_at(SP_SYS_INVALID_FD, path); + sp_path_t best = sp_path(SP_SYS_INVALID_FD, path); s32 deepest = SP_STR_NO_MATCH; for (s32 it = 0;; it++) { sp_sys_fd_t root = sp_sys_get_root(it); @@ -20310,17 +20310,17 @@ sp_path_t sp_path_resolve(sp_str_t path) { sp_str_t rest = sp_str_suffix(path, (s32)path.len - matched); deepest = matched; - best = sp_path_at(root, sp_str_empty(rest) ? sp_str_lit(".") : rest); + best = sp_path(root, sp_str_empty(rest) ? sp_str_lit(".") : rest); } } sp_path_t sp_path_join(sp_mem_t mem, sp_path_t path, sp_str_t sub) { - return sp_path_at(path.dir, sp_fs_join_path(mem, path.sub, sub)); + return sp_path(path.dir, sp_fs_join_path(mem, path.sub, sub)); } sp_path_t sp_path_parent(sp_mem_t mem, sp_path_t path) { if (sp_fs_is_dot(sp_fs_get_name(path.sub))) return sp_path_join(mem, path, sp_str_lit("..")); - return sp_path_at(path.dir, sp_fs_parent_path(path.sub)); + return sp_path(path.dir, sp_fs_parent_path(path.sub)); } sp_err_t sp_fs_open_dir_at(sp_path_t path, sp_sys_fd_t* out) { @@ -20335,7 +20335,7 @@ sp_fs_kind_t sp_fs_get_link_kind_at(sp_path_t path) { } sp_fs_kind_t sp_fs_get_link_kind(sp_str_t path) { - return sp_fs_get_link_kind_at(sp_path_at_cwd(path)); + return sp_fs_get_link_kind_at(sp_path_cwd(path)); } sp_fs_kind_t sp_fs_get_target_kind_at(sp_path_t path) { @@ -20346,7 +20346,7 @@ sp_fs_kind_t sp_fs_get_target_kind_at(sp_path_t path) { } sp_fs_kind_t sp_fs_get_target_kind(sp_str_t path) { - return sp_fs_get_target_kind_at(sp_path_at_cwd(path)); + return sp_fs_get_target_kind_at(sp_path_cwd(path)); } bool sp_fs_exists_at(sp_path_t path) { @@ -20354,7 +20354,7 @@ bool sp_fs_exists_at(sp_path_t path) { } bool sp_fs_exists(sp_str_t path) { - return sp_fs_exists_at(sp_path_at_cwd(path)); + return sp_fs_exists_at(sp_path_cwd(path)); } bool sp_fs_is_file_at(sp_path_t path) { @@ -20362,7 +20362,7 @@ bool sp_fs_is_file_at(sp_path_t path) { } bool sp_fs_is_file(sp_str_t path) { - return sp_fs_is_file_at(sp_path_at_cwd(path)); + return sp_fs_is_file_at(sp_path_cwd(path)); } bool sp_fs_is_symlink_at(sp_path_t path) { @@ -20370,7 +20370,7 @@ bool sp_fs_is_symlink_at(sp_path_t path) { } bool sp_fs_is_symlink(sp_str_t path) { - return sp_fs_is_symlink_at(sp_path_at_cwd(path)); + return sp_fs_is_symlink_at(sp_path_cwd(path)); } bool sp_fs_is_dir_at(sp_path_t path) { @@ -20378,7 +20378,7 @@ bool sp_fs_is_dir_at(sp_path_t path) { } bool sp_fs_is_dir(sp_str_t path) { - return sp_fs_is_dir_at(sp_path_at_cwd(path)); + return sp_fs_is_dir_at(sp_path_cwd(path)); } bool sp_fs_is_target_file_at(sp_path_t path) { @@ -20386,7 +20386,7 @@ bool sp_fs_is_target_file_at(sp_path_t path) { } bool sp_fs_is_target_file(sp_str_t path) { - return sp_fs_is_target_file_at(sp_path_at_cwd(path)); + return sp_fs_is_target_file_at(sp_path_cwd(path)); } bool sp_fs_is_target_dir_at(sp_path_t path) { @@ -20394,7 +20394,7 @@ bool sp_fs_is_target_dir_at(sp_path_t path) { } bool sp_fs_is_target_dir(sp_str_t path) { - return sp_fs_is_target_dir_at(sp_path_at_cwd(path)); + return sp_fs_is_target_dir_at(sp_path_cwd(path)); } sp_fs_kind_t sp_fs_get_kind_at(sp_path_t path) { @@ -20402,7 +20402,7 @@ sp_fs_kind_t sp_fs_get_kind_at(sp_path_t path) { } sp_fs_kind_t sp_fs_get_kind(sp_str_t path) { - return sp_fs_get_kind_at(sp_path_at_cwd(path)); + return sp_fs_get_kind_at(sp_path_cwd(path)); } sp_tm_epoch_t sp_fs_get_mod_time_at(sp_path_t path) { @@ -20416,7 +20416,7 @@ sp_tm_epoch_t sp_fs_get_mod_time_at(sp_path_t path) { } sp_tm_epoch_t sp_fs_get_mod_time(sp_str_t path) { - return sp_fs_get_mod_time_at(sp_path_at_cwd(path)); + return sp_fs_get_mod_time_at(sp_path_cwd(path)); } sp_err_t sp_fs_canonicalize_path_at(sp_mem_t mem, sp_path_t path, sp_str_t* canonical) { @@ -20434,7 +20434,7 @@ sp_err_t sp_fs_canonicalize_path_at(sp_mem_t mem, sp_path_t path, sp_str_t* cano } sp_err_t sp_fs_canonicalize_path(sp_mem_t mem, sp_str_t path, sp_str_t* canonical) { - return sp_fs_canonicalize_path_at(mem, sp_path_at_cwd(path), canonical); + return sp_fs_canonicalize_path_at(mem, sp_path_cwd(path), canonical); } sp_err_t sp_fs_get_exe_path(sp_mem_t mem, sp_str_t* path) { @@ -20503,7 +20503,7 @@ sp_err_t sp_io_read_file_at(sp_mem_t mem, sp_path_t path, sp_str_t* content) { } sp_err_t sp_io_read_file(sp_mem_t mem, sp_str_t path, sp_str_t* content) { - return sp_io_read_file_at(mem, sp_path_at_cwd(path), content); + return sp_io_read_file_at(mem, sp_path_cwd(path), content); } SP_PRIVATE sp_err_t sp_fs_mkdir_at(sp_sys_fd_t dir, sp_str_t path) { @@ -20524,7 +20524,7 @@ sp_err_t sp_fs_create_dir_at(sp_path_t path) { } sp_err_t sp_fs_create_dir(sp_str_t path) { - return sp_fs_create_dir_at(sp_path_at_cwd(path)); + return sp_fs_create_dir_at(sp_path_cwd(path)); } sp_err_t sp_fs_create_file_at(sp_path_t path) { @@ -20534,7 +20534,7 @@ sp_err_t sp_fs_create_file_at(sp_path_t path) { } sp_err_t sp_fs_create_file(sp_str_t path) { - return sp_fs_create_file_at(sp_path_at_cwd(path)); + return sp_fs_create_file_at(sp_path_cwd(path)); } sp_err_t sp_fs_create_file_slice_at(sp_path_t path, sp_mem_slice_t slice) { @@ -20546,7 +20546,7 @@ sp_err_t sp_fs_create_file_slice_at(sp_path_t path, sp_mem_slice_t slice) { } sp_err_t sp_fs_create_file_slice(sp_str_t path, sp_mem_slice_t slice) { - return sp_fs_create_file_slice_at(sp_path_at_cwd(path), slice); + return sp_fs_create_file_slice_at(sp_path_cwd(path), slice); } sp_err_t sp_fs_create_file_str_at(sp_path_t path, sp_str_t str) { @@ -20554,7 +20554,7 @@ sp_err_t sp_fs_create_file_str_at(sp_path_t path, sp_str_t str) { } sp_err_t sp_fs_create_file_str(sp_str_t path, sp_str_t str) { - return sp_fs_create_file_str_at(sp_path_at_cwd(path), str); + return sp_fs_create_file_str_at(sp_path_cwd(path), str); } sp_err_t sp_fs_create_file_cstr_at(sp_path_t path, const c8* str) { @@ -20562,7 +20562,7 @@ sp_err_t sp_fs_create_file_cstr_at(sp_path_t path, const c8* str) { } sp_err_t sp_fs_create_file_cstr(sp_str_t path, const c8* str) { - return sp_fs_create_file_cstr_at(sp_path_at_cwd(path), str); + return sp_fs_create_file_cstr_at(sp_path_cwd(path), str); } sp_err_t sp_fs_create_hard_link_at(sp_path_t target, sp_path_t link_path) { @@ -20570,7 +20570,7 @@ sp_err_t sp_fs_create_hard_link_at(sp_path_t target, sp_path_t link_path) { } sp_err_t sp_fs_create_hard_link(sp_str_t target, sp_str_t link_path) { - return sp_fs_create_hard_link_at(sp_path_at_cwd(target), sp_path_at_cwd(link_path)); + return sp_fs_create_hard_link_at(sp_path_cwd(target), sp_path_cwd(link_path)); } sp_err_t sp_fs_create_sym_link_at(sp_str_t target, sp_path_t link_path, sp_fs_kind_t kind) { @@ -20578,7 +20578,7 @@ sp_err_t sp_fs_create_sym_link_at(sp_str_t target, sp_path_t link_path, sp_fs_ki } sp_err_t sp_fs_create_sym_link(sp_str_t target, sp_str_t link_path, sp_fs_kind_t kind) { - return sp_fs_create_sym_link_at(target, sp_path_at_cwd(link_path), kind); + return sp_fs_create_sym_link_at(target, sp_path_cwd(link_path), kind); } sp_err_t sp_fs_remove_file_at(sp_path_t path) { @@ -20586,7 +20586,7 @@ sp_err_t sp_fs_remove_file_at(sp_path_t path) { } sp_err_t sp_fs_remove_file(sp_str_t path) { - return sp_fs_remove_file_at(sp_path_at_cwd(path)); + return sp_fs_remove_file_at(sp_path_cwd(path)); } sp_err_t sp_fs_dir_it_open(sp_fs_dir_it_t* it, sp_path_t path, u32 flags, sp_mem_slice_t buf) { @@ -20678,7 +20678,7 @@ SP_PRIVATE sp_err_t sp_fs_it_step(sp_fs_it_t* it) { .name = sp_str(it->path + len - d.name.len, d.name.len), .kind = d.kind, }; - it->at = sp_path_at(top->dir.dir.fd, it->entry.name); + it->at = sp_path(top->dir.dir.fd, it->entry.name); // If the OS didn't give us a hint, specifically get it from metadata if (it->entry.kind == SP_FS_KIND_NONE) { @@ -20699,7 +20699,7 @@ SP_PRIVATE sp_err_t sp_fs_it_step(sp_fs_it_t* it) { .name = sp_str(it->path + top->len - top->name_len, top->name_len), .kind = SP_FS_KIND_DIR, }; - it->at = sp_path_at(parent->dir.dir.fd, it->entry.name); + it->at = sp_path(parent->dir.dir.fd, it->entry.name); return SP_OK; } @@ -20747,7 +20747,7 @@ sp_fs_it_t sp_fs_it_new_at(sp_mem_t mem, sp_path_t path, u32 flags) { } sp_fs_it_t sp_fs_it_new(sp_mem_t mem, sp_str_t path) { - return sp_fs_it_new_at(mem, sp_path_at_cwd(path), 0); + return sp_fs_it_new_at(mem, sp_path_cwd(path), 0); } SP_PRIVATE sp_fs_entry_t sp_fs_entry_copy(sp_mem_t mem, sp_fs_entry_t entry) { @@ -20781,7 +20781,7 @@ sp_err_t sp_fs_collect_recursive(sp_mem_t mem, sp_str_t path, sp_da(sp_fs_entry_ } sp_err_t sp_fs_remove_dir(sp_str_t path) { - return sp_fs_remove_dir_at(sp_path_at_cwd(path)); + return sp_fs_remove_dir_at(sp_path_cwd(path)); } /* @@ -20940,7 +20940,7 @@ SP_PRIVATE sp_err_t sp_fs_temp_name(sp_str_t dir, sp_str_t name, c8 buf [SP_PATH SP_API sp_err_t sp_fs_create_parent_at(sp_path_t path) { sp_str_t parent = sp_fs_parent_path(path.sub); if (sp_str_equal(parent, sp_fs_trim_path(path.sub))) return SP_OK; - sp_err_t err = sp_fs_create_dir_at(sp_path_at(path.dir, parent)); + sp_err_t err = sp_fs_create_dir_at(sp_path(path.dir, parent)); return err == SP_ERR_SYS_EXISTS ? SP_ERR_SYS_NOT_DIR : err; } @@ -20971,7 +20971,7 @@ sp_err_t sp_fs_atomic_open_at(sp_fs_atomic_t* af, sp_path_t path) { } sp_err_t sp_fs_atomic_open(sp_fs_atomic_t* af, sp_str_t path) { - return sp_fs_atomic_open_at(af, sp_path_at_cwd(path)); + return sp_fs_atomic_open_at(af, sp_path_cwd(path)); } sp_err_t sp_fs_atomic_open_staged_at(sp_fs_atomic_t* af, sp_path_t path, sp_str_t staging) { @@ -20980,7 +20980,7 @@ sp_err_t sp_fs_atomic_open_staged_at(sp_fs_atomic_t* af, sp_path_t path, sp_str_ } sp_err_t sp_fs_atomic_open_staged(sp_fs_atomic_t* af, sp_str_t path, sp_str_t staging) { - return sp_fs_atomic_open_staged_at(af, sp_path_at_cwd(path), staging); + return sp_fs_atomic_open_staged_at(af, sp_path_cwd(path), staging); } sp_io_writer_t* sp_fs_atomic_writer(sp_fs_atomic_t* af) { @@ -21037,7 +21037,7 @@ sp_err_t sp_fs_write_atomic_slice_staged_at(sp_path_t path, sp_str_t staging, sp } sp_err_t sp_fs_write_atomic_slice_staged(sp_str_t path, sp_str_t staging, sp_mem_slice_t slice) { - return sp_fs_write_atomic_slice_staged_at(sp_path_at_cwd(path), staging, slice); + return sp_fs_write_atomic_slice_staged_at(sp_path_cwd(path), staging, slice); } sp_err_t sp_fs_write_atomic_slice_at(sp_path_t path, sp_mem_slice_t slice) { @@ -21047,7 +21047,7 @@ sp_err_t sp_fs_write_atomic_slice_at(sp_path_t path, sp_mem_slice_t slice) { } sp_err_t sp_fs_write_atomic_slice(sp_str_t path, sp_mem_slice_t slice) { - return sp_fs_write_atomic_slice_at(sp_path_at_cwd(path), slice); + return sp_fs_write_atomic_slice_at(sp_path_cwd(path), slice); } sp_err_t sp_fs_write_atomic_at(sp_path_t path, sp_str_t str) { @@ -21057,7 +21057,7 @@ sp_err_t sp_fs_write_atomic_at(sp_path_t path, sp_str_t str) { } sp_err_t sp_fs_write_atomic(sp_str_t path, sp_str_t str) { - return sp_fs_write_atomic_at(sp_path_at_cwd(path), str); + return sp_fs_write_atomic_at(sp_path_cwd(path), str); } sp_err_t sp_fs_write_atomic_cstr_at(sp_path_t path, const c8* str) { @@ -21065,7 +21065,7 @@ sp_err_t sp_fs_write_atomic_cstr_at(sp_path_t path, const c8* str) { } sp_err_t sp_fs_write_atomic_cstr(sp_str_t path, const c8* str) { - return sp_fs_write_atomic_cstr_at(sp_path_at_cwd(path), str); + return sp_fs_write_atomic_cstr_at(sp_path_cwd(path), str); } SP_PRIVATE sp_err_t sp_fs_copy_link_at(sp_path_t from, sp_path_t to, sp_fs_atomic_mode_t mode) { @@ -21139,7 +21139,7 @@ sp_err_t sp_fs_copy_tree_at(sp_path_t from, sp_path_t to, sp_fs_atomic_mode_t mo if (!err) err = sp_fs_open_dir_at(to, &fd); while (!err && sp_fs_it_walk(&it)) { - sp_path_t dst = sp_path_at(fd, it.entry.rel); + sp_path_t dst = sp_path(fd, it.entry.rel); switch (it.entry.kind) { case SP_FS_KIND_DIR: err = sp_fs_create_dir_at(dst); break; case SP_FS_KIND_FILE: err = sp_fs_copy_file_at(it.at, dst, mode); break; diff --git a/sp/sp_msvc.h b/sp/sp_msvc.h index a8cb6bf8..499d87b1 100644 --- a/sp/sp_msvc.h +++ b/sp/sp_msvc.h @@ -453,7 +453,7 @@ static sp_msvc_err_t sp_msvc_find_sdks(sp_msvc_t* msvc, sp_msvc_arch_t arch) { SP_ALIGNED u8 dir_buf [SP_SYS_DIR_MIN_BUF]; sp_fs_dir_it_t dir = sp_zero; - if (sp_fs_dir_it_open(&dir, sp_path_at_cwd(lib), 0, sp_mem_slice(dir_buf, sizeof(dir_buf)))) { + if (sp_fs_dir_it_open(&dir, sp_path_cwd(lib), 0, sp_mem_slice(dir_buf, sizeof(dir_buf)))) { return SP_MSVC_ERR_SDK_NOT_FOUND; } @@ -500,7 +500,7 @@ static sp_msvc_err_t sp_msvc_find_installations(sp_msvc_t* msvc, sp_msvc_arch_t SP_ALIGNED u8 dir_buf [SP_SYS_DIR_MIN_BUF]; sp_fs_dir_it_t dir = sp_zero; - if (sp_fs_dir_it_open(&dir, sp_path_at_cwd(instances.value), 0, sp_mem_slice(dir_buf, sizeof(dir_buf)))) { + if (sp_fs_dir_it_open(&dir, sp_path_cwd(instances.value), 0, sp_mem_slice(dir_buf, sizeof(dir_buf)))) { return SP_MSVC_ERR_VS_NOT_FOUND; } diff --git a/sp/sp_test.h b/sp/sp_test.h index 2da3c96a..c8d82143 100644 --- a/sp/sp_test.h +++ b/sp/sp_test.h @@ -1096,9 +1096,9 @@ static sp_path_t sp_test_golden_root(sp_test_t* t, sp_str_t file) { sp_mutex_unlock(&runner->mutex); if (!sp_str_empty(root.sub)) return root; - sp_path_t anchors [2] = { sp_path_at_cwd(sp_str_lit(".")) }; + sp_path_t anchors [2] = { sp_path_cwd(sp_str_lit(".")) }; sp_str_t exe = sp_zero; - if (!sp_fs_get_exe_path(t->mem, &exe)) anchors[1] = sp_path_resolve(sp_fs_parent_path(exe)); + if (!sp_fs_get_exe_path(t->mem, &exe)) anchors[1] = sp_path_from_str(sp_fs_parent_path(exe)); sp_carr_for(anchors, it) { if (sp_str_empty(anchors[it].sub)) continue; @@ -1109,7 +1109,7 @@ static sp_path_t sp_test_golden_root(sp_test_t* t, sp_str_t file) { sp_mutex_lock(&runner->mutex); if (sp_str_empty(runner->golden_root.sub)) { - runner->golden_root = sp_path_at(root.dir, sp_str_copy(runner->mem, root.sub)); + runner->golden_root = sp_path(root.dir, sp_str_copy(runner->mem, root.sub)); } root = runner->golden_root; sp_mutex_unlock(&runner->mutex); @@ -1118,7 +1118,7 @@ static sp_path_t sp_test_golden_root(sp_test_t* t, sp_str_t file) { static void sp_test_golden_at(sp_test_t* t, sp_path_t path, sp_str_t actual, sp_str_t file, u32 line) { sp_str_t shown = sp_test_path_str(t->mem, path); - sp_path_t actual_path = sp_path_at(path.dir, sp_test_format(t, "{}.actual", sp_fmt_str(path.sub))); + sp_path_t actual_path = sp_path(path.dir, sp_test_format(t, "{}.actual", sp_fmt_str(path.sub))); if (t->runner->update) { sp_fs_create_dir_at(sp_path_parent(t->mem, path)); @@ -1195,7 +1195,7 @@ void sp_test_golden(sp_test_t* t, sp_str_t path, sp_str_t actual, sp_str_t file, sp_path_t src = sp_zero; if (sp_fs_is_absolute_for(file, SP_FS_PATH_WINDOWS)) { - src = sp_path_resolve(file); + src = sp_path_from_str(file); } else { sp_path_t root = sp_test_golden_root(t, file); @@ -1222,7 +1222,7 @@ void sp_test_golden(sp_test_t* t, sp_str_t path, sp_str_t actual, sp_str_t file, } sp_path_t golden = sp_fs_is_absolute(path) ? - sp_path_resolve(path) : + sp_path_from_str(path) : sp_path_join(t->mem, sp_path_parent(t->mem, src), path); sp_test_golden_at(t, golden, actual, file, line); } @@ -2016,7 +2016,7 @@ s32 sp_test_main(s32 argc, const c8** argv, const sp_test_entry_t* entries) { if (golden.env) golden_root = sp_cstr_as_str(golden.env); if (golden.opt) golden_root = sp_cstr_as_str(golden.opt); if (!sp_str_empty(golden_root)) { - runner->golden_root = sp_path_resolve(sp_fs_normalize_path(runner->mem, golden_root)); + runner->golden_root = sp_path_from_str(sp_fs_normalize_path(runner->mem, golden_root)); if (runner->golden_root.dir == SP_SYS_INVALID_FD) { sp_fmt_io(&runner->out.base, "no root serves {.quote}\n", sp_fmt_str(golden_root)); sp_io_flush(&runner->out.base); @@ -2114,7 +2114,7 @@ s32 sp_test_main(s32 argc, const c8** argv, const sp_test_entry_t* entries) { if (dir.opt) dir_name = sp_cstr_as_str(dir.opt); dir_name = sp_fs_normalize_path(runner->mem, dir_name); - sp_path_t root = sp_path_resolve(dir_name); + sp_path_t root = sp_path_from_str(dir_name); if (root.dir == SP_SYS_INVALID_FD) { sp_fmt_io(&runner->out.base, "no root serves {.quote}\n", sp_fmt_str(dir_name)); sp_io_flush(&runner->out.base); diff --git a/test/fs/atomic.c b/test/fs/atomic.c index 1f457cfe..ab9e9078 100644 --- a/test/fs/atomic.c +++ b/test/fs/atomic.c @@ -246,7 +246,7 @@ sp_test_each(fs, atomic_open_at, test_t, open_at_tests) { sp_must_ok(t, sp_fs_open_dir_at(sandbox, &dir)); sp_fs_atomic_t af = sp_zero; - sp_err_t err = drive(&af, sp_fs_atomic_open_at(&af, sp_path_at(dir, sp_str_view(it->path))), it); + sp_err_t err = drive(&af, sp_fs_atomic_open_at(&af, sp_path(dir, sp_str_view(it->path))), it); sp_sys_close(dir); sp_expect_err_eq(t, err, it->expect.err); @@ -262,7 +262,7 @@ sp_test_each(fs, atomic_staged, test_t, staged_tests) { sp_sys_fd_t dir = SP_SYS_INVALID_FD; sp_must_ok(t, sp_fs_open_dir_at(sandbox, &dir)); - sp_path_t path = sp_path_at(dir, sp_str_view(it->path)); + sp_path_t path = sp_path(dir, sp_str_view(it->path)); sp_fs_atomic_t af = sp_zero; sp_err_t err = drive(&af, sp_fs_atomic_open_staged_at(&af, path, sp_str_lit("S")), it); diff --git a/test/fs/canonicalize.c b/test/fs/canonicalize.c index 77463863..5ddef11b 100644 --- a/test/fs/canonicalize.c +++ b/test/fs/canonicalize.c @@ -167,7 +167,7 @@ sp_test_each(fs, canonicalize, test_t, tests) { sp_sys_fd_t dir = SP_SYS_INVALID_FD; sp_must_ok(t, sp_fs_open_dir_at(sandbox, &dir)); - sp_path_t input = sp_path_at(dir, sp_str_view(it->input)); + sp_path_t input = sp_path(dir, sp_str_view(it->input)); sp_str_t result = sp_zero; sp_expect_err_eq(t, sp_fs_canonicalize_path_at(mem, input, &result), it->expect.err); @@ -187,16 +187,16 @@ sp_test_each(fs, canonicalize, test_t, tests) { sp_expect_str_eq_c(t, sp_fs_get_name(result), it->expect.name); } if (it->expect.exists) { - sp_expect(t, sp_fs_exists_at(sp_path_at(dir, result))); + sp_expect(t, sp_fs_exists_at(sp_path(dir, result))); } if (it->expect.idempotent) { sp_str_t again = sp_zero; - sp_expect_ok(t, sp_fs_canonicalize_path_at(mem, sp_path_at(dir, result), &again)); + sp_expect_ok(t, sp_fs_canonicalize_path_at(mem, sp_path(dir, result), &again)); sp_expect_str_eq(t, again, result); } if (it->expect.same_as) { sp_str_t other = sp_zero; - sp_expect_ok(t, sp_fs_canonicalize_path_at(mem, sp_path_at(dir, sp_str_view(it->expect.same_as)), &other)); + sp_expect_ok(t, sp_fs_canonicalize_path_at(mem, sp_path(dir, sp_str_view(it->expect.same_as)), &other)); sp_expect_str_eq(t, result, other); } sp_sys_close(dir); diff --git a/test/fs/copy.c b/test/fs/copy.c index 0da774f0..4bdc2a4f 100644 --- a/test/fs/copy.c +++ b/test/fs/copy.c @@ -542,8 +542,8 @@ sp_test_each(fs, copy, test_t, tests) { sp_sys_fd_t dst_dir = SP_SYS_INVALID_FD; sp_must_ok(t, sp_fs_open_dir_at(dst_base, &dst_dir)); - sp_path_t src = sp_path_at(dir, sp_cstr_as_str(it->src)); - sp_path_t dst = sp_path_at(dst_dir, sp_cstr_as_str(it->dst)); + sp_path_t src = sp_path(dir, sp_cstr_as_str(it->src)); + sp_path_t dst = sp_path(dst_dir, sp_cstr_as_str(it->dst)); sp_err_t result = SP_OK; switch (it->op) { diff --git a/test/fs/create_dir_sim.c b/test/fs/create_dir_sim.c index 5f5d5b7c..97f6f3ee 100644 --- a/test/fs/create_dir_sim.c +++ b/test/fs/create_dir_sim.c @@ -30,7 +30,7 @@ static sp_err_t run(sp_test_t* t, test_t* c) { mkdirs = 0; const sp_sys_vtable_t* saved = sp_sys_set_vtable(&vt); - sp_err_t err = sp_fs_create_dir_at(sp_path_at_cwd(sp_cstr_as_str(c->target))); + sp_err_t err = sp_fs_create_dir_at(sp_path_cwd(sp_cstr_as_str(c->target))); sp_sys_set_vtable(saved); sp_expect_err_eq(t, err, SP_ERR_SYS_NOT_FOUND); diff --git a/test/fs/dir/os.c b/test/fs/dir/os.c index dc97c11a..bd1918d5 100644 --- a/test/fs/dir/os.c +++ b/test/fs/dir/os.c @@ -115,7 +115,7 @@ sp_test_each(fs, dir, test_t, tests) { SP_ALIGNED u8 buf [SP_SYS_DIR_MIN_BUF]; sp_fs_dir_it_t iter = sp_zero; - sp_path_t opened = it->relative ? sp_path_at(sandbox_fd, sp_cstr_as_str(it->dir)) : dir; + sp_path_t opened = it->relative ? sp_path(sandbox_fd, sp_cstr_as_str(it->dir)) : dir; sp_err_t open_err = sp_fs_dir_it_open(&iter, opened, 0, sp_mem_slice(buf, sizeof(buf))); sp_expect_ok(t, open_err); diff --git a/test/fs/dir/sim.c b/test/fs/dir/sim.c index 6821f4f5..c6f6139e 100644 --- a/test/fs/dir/sim.c +++ b/test/fs/dir/sim.c @@ -42,7 +42,7 @@ sp_test_each(fs, dir_sim, test_t, tests, .serial = true) { SP_ALIGNED u8 buf [SP_SYS_DIR_MIN_BUF]; sp_fs_dir_it_t dir = sp_zero; - sp_err_t open_err = sp_fs_dir_it_open(&dir, sp_path_at(sp_sys_get_root(0), sp_str_lit("T")), 0, sp_mem_slice(buf, sizeof(buf))); + sp_err_t open_err = sp_fs_dir_it_open(&dir, sp_path(sp_sys_get_root(0), sp_str_lit("T")), 0, sp_mem_slice(buf, sizeof(buf))); sp_expect_err_eq(t, open_err, it->expect.open); if (!open_err) { diff --git a/test/fs/get_cwd.c b/test/fs/get_cwd.c index 06794f03..0a162274 100644 --- a/test/fs/get_cwd.c +++ b/test/fs/get_cwd.c @@ -24,7 +24,7 @@ sp_test(fs, get_cwd_unlinked_cwd_has_no_path, .serial = true) { sp_sys_fd_t dir = SP_SYS_INVALID_FD; sp_must_ok(t, sp_fs_open_dir_at(sandbox, &dir)); - sp_path_t doomed = sp_path_at(dir, sp_str_lit("A")); + sp_path_t doomed = sp_path(dir, sp_str_lit("A")); sp_str_t canonical = sp_zero; sp_err_t err = sp_fs_create_dir_at(doomed); if (!err) err = sp_fs_canonicalize_path_at(mem, doomed, &canonical); @@ -50,16 +50,16 @@ sp_test(fs, get_cwd_is_root_zero) { return SP_OK; } -sp_test(fs, path_at_cwd_passes_through) { +sp_test(fs, path_cwd_passes_through) { sp_test_skip_on_wasm() sp_path_t file = sp_path_join(sp_test_arena(t), sp_test_dir(t), sp_str_lit("A")); sp_must_ok(t, sp_fs_create_file_at(file)); - sp_path_t path = sp_path_at_cwd(file.sub); + sp_path_t path = sp_path_cwd(file.sub); sp_expect_eq(t, path.dir, sp_fs_get_cwd()); sp_expect_str_eq(t, path.sub, file.sub); sp_expect(t, sp_fs_is_file_at(path)); - sp_expect(t, sp_fs_is_dir_at(sp_path_at_cwd(sp_str_lit(".")))); + sp_expect(t, sp_fs_is_dir_at(sp_path_cwd(sp_str_lit(".")))); return SP_OK; } diff --git a/test/fs/it/enter_sim.c b/test/fs/it/enter_sim.c index 77e236a1..d9ea4ef9 100644 --- a/test/fs/it/enter_sim.c +++ b/test/fs/it/enter_sim.c @@ -130,7 +130,7 @@ sp_test_each(fs, it_enter_sim, test_t, tests, .serial = true) { u32 leaves = 0; u32 produced = 0; bool used [MAX_YIELDS] = sp_zero; - sp_fs_it_t walk = sp_fs_it_new_at(mem, sp_path_at(sp_sys_get_root(0), sp_str_lit("T")), 0); + sp_fs_it_t walk = sp_fs_it_new_at(mem, sp_path(sp_sys_get_root(0), sp_str_lit("T")), 0); while (sp_fs_it_next(&walk)) { if (produced < MAX_YIELDS && it->expect.yields[produced].path) { const yield_t* want = &it->expect.yields[produced]; diff --git a/test/fs/it/os.c b/test/fs/it/os.c index 8863b0ea..fae60093 100644 --- a/test/fs/it/os.c +++ b/test/fs/it/os.c @@ -171,7 +171,7 @@ sp_test_each(fs, it, test_t, tests) { sp_must_ok(t, sp_fs_open_dir_at(sandbox, &dir)); sp_str_t base = sp_str_lit("R"); - sp_path_t root = sp_path_at(dir, sp_cstr_as_str(it->root ? it->root : "R")); + sp_path_t root = sp_path(dir, sp_cstr_as_str(it->root ? it->root : "R")); fs_match_t matches [FS_MAX_PATHS] = sp_zero; u32 n = 0; diff --git a/test/fs/it/sim.c b/test/fs/it/sim.c index 185f8412..8ba0377f 100644 --- a/test/fs/it/sim.c +++ b/test/fs/it/sim.c @@ -192,7 +192,7 @@ sp_test_each(fs, it_sim, test_t, tests, .serial = true) { sim_t s = sp_zero; sim_begin(&s, it->dirs); - sp_fs_it_t walk = sp_fs_it_new_at(mem, sp_path_at(sp_sys_get_root(0), root), it->flags); + sp_fs_it_t walk = sp_fs_it_new_at(mem, sp_path(sp_sys_get_root(0), root), it->flags); sp_expect_err_eq(t, walk.err, it->expect.open); sp_expect(t, !walk.entry.path.data); diff --git a/test/fs/resolve.c b/test/fs/path_from_str.c similarity index 97% rename from test/fs/resolve.c rename to test/fs/path_from_str.c index 8996e2da..01d46eb7 100644 --- a/test/fs/resolve.c +++ b/test/fs/path_from_str.c @@ -169,7 +169,7 @@ static sp_err_t run(sp_test_t* t, test_t* c) { active = c; const sp_sys_vtable_t* saved = sp_sys_set_vtable(&vt); - sp_path_t path = sp_path_resolve(sp_cstr_as_str(c->path)); + sp_path_t path = sp_path_from_str(sp_cstr_as_str(c->path)); sp_sys_set_vtable(saved); active = SP_NULLPTR; @@ -181,4 +181,4 @@ static sp_err_t run(sp_test_t* t, test_t* c) { return SP_OK; } -sp_test_each_fn(fs, resolve, test_t, tests, run, .serial = true); +sp_test_each_fn(fs, path_from_str, test_t, tests, run, .serial = true); diff --git a/test/fs/path_join.c b/test/fs/path_join.c index 9b868f76..3a30f4fe 100644 --- a/test/fs/path_join.c +++ b/test/fs/path_join.c @@ -21,7 +21,7 @@ static const test_t tests [] = { }; sp_test_each(fs, path_join, test_t, tests) { - sp_path_t path = sp_path_at((sp_sys_fd_t)DIR_FD, sp_cstr_as_str(it->a)); + sp_path_t path = sp_path((sp_sys_fd_t)DIR_FD, sp_cstr_as_str(it->a)); sp_path_t result = sp_path_join(sp_test_arena(t), path, sp_cstr_as_str(it->b)); sp_expect_eq(t, result.dir, (sp_sys_fd_t)DIR_FD); sp_expect_str_eq_c(t, result.sub, it->expect); diff --git a/test/fs/path_parent.c b/test/fs/path_parent.c index 2e15c5d3..34e4bf49 100644 --- a/test/fs/path_parent.c +++ b/test/fs/path_parent.c @@ -25,7 +25,7 @@ static const test_t tests [] = { }; sp_test_each(fs, path_parent, test_t, tests) { - sp_path_t path = sp_path_at((sp_sys_fd_t)DIR_FD, sp_cstr_as_str(it->sub)); + sp_path_t path = sp_path((sp_sys_fd_t)DIR_FD, sp_cstr_as_str(it->sub)); sp_path_t result = sp_path_parent(sp_test_arena(t), path); sp_expect_eq(t, result.dir, (sp_sys_fd_t)DIR_FD); sp_expect_str_eq_c(t, result.sub, it->expect); diff --git a/test/fs/remove/os.c b/test/fs/remove/os.c index c0e55740..48339fb3 100644 --- a/test/fs/remove/os.c +++ b/test/fs/remove/os.c @@ -312,7 +312,7 @@ sp_test_each(fs, remove, test_t, tests) { sp_sys_fd_t dir = SP_SYS_INVALID_FD; sp_must_ok(t, sp_fs_open_dir_at(base, &dir)); - sp_path_t path = sp_path_at(dir, sp_cstr_as_str(it->path)); + sp_path_t path = sp_path(dir, sp_cstr_as_str(it->path)); sp_err_t result = it->dir ? sp_fs_remove_dir_at(path) : sp_fs_remove_file_at(path); sp_expect_err_eq(t, result, it->expect.err); @@ -331,7 +331,7 @@ sp_test(fs, remove_dir_deeper_than_path_max_is_name_too_long) { sp_sys_fd_t cur = SP_SYS_INVALID_FD; sp_must_ok(t, sp_fs_open_dir_at(root, &cur)); sp_for(level, DEEP_LEVELS) { - sp_path_t child = sp_path_at(cur, sp_str_lit(DEEP_NAME)); + sp_path_t child = sp_path(cur, sp_str_lit(DEEP_NAME)); sp_must_ok(t, sp_fs_create_dir_at(child)); sp_sys_fd_t next = SP_SYS_INVALID_FD; @@ -339,7 +339,7 @@ sp_test(fs, remove_dir_deeper_than_path_max_is_name_too_long) { sp_sys_close(cur); cur = next; } - sp_must_ok(t, sp_fs_create_file_at(sp_path_at(cur, sp_str_lit("F")))); + sp_must_ok(t, sp_fs_create_file_at(sp_path(cur, sp_str_lit("F")))); sp_sys_close(cur); sp_expect_err_eq(t, sp_fs_remove_dir_at(root), SP_ERR_SYS_NAME_TOO_LONG); @@ -352,7 +352,7 @@ sp_test(fs, remove_dir_deeper_than_path_max_is_name_too_long) { sp_sys_close(cur); cur = next; } - sp_expect_ok(t, sp_fs_remove_dir_at(sp_path_at(cur, sp_str_lit(DEEP_NAME)))); + sp_expect_ok(t, sp_fs_remove_dir_at(sp_path(cur, sp_str_lit(DEEP_NAME)))); sp_sys_close(cur); sp_expect_ok(t, sp_fs_remove_dir_at(root)); diff --git a/test/fs/remove/sim.c b/test/fs/remove/sim.c index 47d62842..8b2d9e2d 100644 --- a/test/fs/remove/sim.c +++ b/test/fs/remove/sim.c @@ -294,7 +294,7 @@ static const test_t tests [] = { sp_test_each(fs, remove_sim, test_t, tests, .serial = true) { sim_t s = sp_zero; sim_begin(&s, it->dirs); - sp_err_t err = sp_fs_remove_dir_at(sp_path_at(sp_sys_get_root(0), sp_str_lit("T"))); + sp_err_t err = sp_fs_remove_dir_at(sp_path(sp_sys_get_root(0), sp_str_lit("T"))); sim_end(&s); sp_expect_err_eq(t, err, it->expect.err); diff --git a/test/io/file.c b/test/io/file.c index 98136d1b..9425fc03 100644 --- a/test/io/file.c +++ b/test/io/file.c @@ -132,14 +132,14 @@ UTEST_F(io, file_reader_buffered_seek_discards_buffer) { } UTEST_F(io, file_reader_nonexistent) { - sp_path_t path = sp_path_at(ut.file.dir, sp_str_lit("nonexistent.file")); + sp_path_t path = sp_path(ut.file.dir, sp_str_lit("nonexistent.file")); sp_io_file_reader_t r = sp_zero; EXPECT_EQ(sp_io_file_reader_from_path_at(&r, path), SP_ERR_SYS_NOT_FOUND); sp_io_file_reader_close(&r); } UTEST_F(io, file_writer_nonexistent_dir) { - sp_path_t path = sp_path_at(ut.file.dir, sp_str_lit("missing-dir/nested.file")); + sp_path_t path = sp_path(ut.file.dir, sp_str_lit("missing-dir/nested.file")); sp_io_file_writer_t w = sp_zero; EXPECT_EQ(sp_io_file_writer_from_path_at(&w, path), SP_ERR_SYS_NOT_FOUND); } @@ -370,7 +370,7 @@ UTEST_F(io, file_to_file_copy) { EXPECT_EQ(sp_io_write(&sw.base, source, sizeof(source), SP_NULLPTR), SP_OK); sp_io_file_writer_close(&sw); - sp_path_t dst_path = sp_path_at(ut.file.dir, sp_str_lit("file_to_file_copy.dst")); + sp_path_t dst_path = sp_path(ut.file.dir, sp_str_lit("file_to_file_copy.dst")); sp_io_file_reader_t r = sp_zero; sp_io_file_reader_from_path_at(&r, ut.file); @@ -402,7 +402,7 @@ UTEST_F(io, stream_file_to_file_copy) { EXPECT_EQ(sp_io_write(&sw.base, source, sizeof(source), SP_NULLPTR), SP_OK); sp_io_file_writer_close(&sw); - sp_path_t dst_path = sp_path_at(ut.file.dir, sp_str_lit("stream_file_to_file_copy.dst")); + sp_path_t dst_path = sp_path(ut.file.dir, sp_str_lit("stream_file_to_file_copy.dst")); sp_sys_fd_t file = SP_SYS_INVALID_FD; ASSERT_EQ(sp_sys_open_s(ut.file.dir, ut.file.sub, SP_SYS_OPEN_MODE_RO, 0, &file), SP_OK); diff --git a/test/sys/fd_path.c b/test/sys/fd_path.c index 5bcdaff2..37baa3b4 100644 --- a/test/sys/fd_path.c +++ b/test/sys/fd_path.c @@ -149,7 +149,7 @@ static sp_err_t run(sp_test_t* t, test_t* c) { break; } case TARGET_CWD: { - expect = sp_path_at_cwd(sp_cstr_as_str(c->expect.path)); + expect = sp_path_cwd(sp_cstr_as_str(c->expect.path)); fd = expect.dir; break; } @@ -195,7 +195,7 @@ static sp_err_t run(sp_test_t* t, test_t* c) { sp_expect_err_eq(t, err, c->expect.err); if (!err) { sp_expect_eq(t, buf[len], 0); - sp_expect(t, same_file(expect, sp_path_at_cwd(sp_str(buf, (u32)len)))); + sp_expect(t, same_file(expect, sp_path_cwd(sp_str(buf, (u32)len)))); } switch (c->target) { diff --git a/test/test/golden.c b/test/test/golden.c index 74e40813..32b221c0 100644 --- a/test/test/golden.c +++ b/test/test/golden.c @@ -106,7 +106,7 @@ sp_test_each(runner, golden, test_t, tests) { if (it->update) sp_must_ok(t, sp_fs_create_file_cstr_at(actual, "stale")); sp_str_t cwd = it->cwd ? sp_fs_join_path(mem, sandbox.sub, sp_cstr_as_str(it->cwd)) : sandbox.sub; - sp_must_ok(t, sp_fs_create_dir_at(sp_path_at(sandbox.dir, cwd))); + sp_must_ok(t, sp_fs_create_dir_at(sp_path(sandbox.dir, cwd))); sp_ps_config_t config = { .command = exe, diff --git a/tools/sp.c b/tools/sp.c index 88e62308..28b9719f 100644 --- a/tools/sp.c +++ b/tools/sp.c @@ -160,7 +160,7 @@ static sp_da(const target_t*) target_resolve(sp_mem_t mem, sp_str_t query) { static sp_da(sp_str_t) tests_discover(sp_mem_t mem, sp_str_t dir, sp_str_t ext) { sp_da(sp_str_t) names = sp_da_new(mem, sp_str_t); - sp_fs_for(mem, sp_path_resolve(dir), it) { + sp_fs_for(mem, sp_path_from_str(dir), it) { sp_str_t name = it.entry.name; if (!sp_str_empty(ext)) { if (!sp_str_ends_with(name, ext)) continue;