From 4ae51d7a237edabdb2c175b4ba2f5704ab37f277 Mon Sep 17 00:00:00 2001 From: Martin Fouilleul Date: Tue, 9 May 2023 18:44:23 +0200 Subject: [PATCH] [file io, wip] open/close file API --- build.sh | 9 +- samples/pong/build.sh | 2 +- samples/pong/src/main.c | 3 + scripts/mkapp.py | 2 + sdk/io.c | 95 ++++++++++++++++ sdk/io.h | 28 +++++ sdk/orca.c | 2 + sdk/orca.h | 1 + src/io_api.json | 9 ++ src/io_common.h | 88 ++++++++++++++ src/io_impl.c | 247 ++++++++++++++++++++++++++++++++++++++++ src/main.c | 6 +- 12 files changed, 488 insertions(+), 4 deletions(-) create mode 100644 sdk/io.c create mode 100644 sdk/io.h create mode 100644 src/io_api.json create mode 100644 src/io_common.h create mode 100644 src/io_impl.c diff --git a/build.sh b/build.sh index e554a08..f755e04 100755 --- a/build.sh +++ b/build.sh @@ -29,7 +29,7 @@ elif [ $target = wasm3 ] ; then for file in ./ext/wasm3/source/*.c ; do name=$(basename $file) name=${name/.c/.o} - clang -c -g -Wno-extern-initializer -Dd_m3VerboseErrorMessages -o ./bin/obj/$name -I./ext/wasm3/source $file + clang -c -g -foptimize-sibling-calls -Wno-extern-initializer -Dd_m3VerboseErrorMessages -o ./bin/obj/$name -I./ext/wasm3/source $file done ar -rcs ./bin/libwasm3.a ./bin/obj/*.o rm -rf ./bin/obj @@ -43,7 +43,7 @@ elif [ $target = orca ] ; then cp milepost/bin/libGLESv2.dylib bin/ cp milepost/bin/libEGL.dylib bin/ - INCLUDES="-Imilepost/src -Imilepost/src/util -Imilepost/src/platform -Iext/wasm3/source -Imilepost/ext/" + INCLUDES="-Isrc -Isdk -Imilepost/src -Imilepost/src/util -Imilepost/src/platform -Iext/wasm3/source -Imilepost/ext/" LIBS="-Lbin -lmilepost -lwasm3" FLAGS="-g -DLOG_COMPILE_DEBUG -mmacos-version-min=10.15.4 -maes" @@ -57,6 +57,11 @@ elif [ $target = orca ] ; then --guest-include graphics.h \ --wasm3-bindings ./src/canvas_api_bind_gen.c + python3 ./scripts/bindgen2.py io \ + src/io_api.json \ + --guest-stubs sdk/io_stubs.c \ + --wasm3-bindings ./src/io_api_bind_gen.c + # compile orca clang $FLAGS $INCLUDES $LIBS -o bin/orca src/main.c diff --git a/samples/pong/build.sh b/samples/pong/build.sh index 78b8d6d..74fe697 100755 --- a/samples/pong/build.sh +++ b/samples/pong/build.sh @@ -8,7 +8,7 @@ wasmFlags="--target=wasm32 \ -Wl,--allow-undefined \ -g \ -D__ORCA__ \ - -I ../../sdk -I../../milepost/ext -I ../../milepost -I ../../milepost/src -I ../../milepost/src/util -I ../../milepost/src/platform -I../.." + -I ../../src -I ../../sdk -I../../milepost/ext -I ../../milepost -I ../../milepost/src -I ../../milepost/src/util -I ../../milepost/src/platform -I../.." /usr/local/opt/llvm/bin/clang $wasmFlags -o ./module.wasm ../../sdk/orca.c src/main.c diff --git a/samples/pong/src/main.c b/samples/pong/src/main.c index 0fc1381..ae82918 100644 --- a/samples/pong/src/main.c +++ b/samples/pong/src/main.c @@ -40,6 +40,9 @@ void OnInit(void) //TODO create surface for main window surface = mg_surface_main(); canvas = mg_canvas_create(); + + file_handle file = file_open(STR8("test_file.txt") , IO_OPEN_CREATE | IO_OPEN_WRITE); + file_close(file); } void OnFrameResize(u32 width, u32 height) diff --git a/scripts/mkapp.py b/scripts/mkapp.py index bb00365..6dcb3f3 100644 --- a/scripts/mkapp.py +++ b/scripts/mkapp.py @@ -39,6 +39,7 @@ contents_dir = bundle_path + '/Contents' exe_dir = contents_dir + '/MacOS' res_dir = contents_dir + '/resources' wasm_dir = contents_dir + '/wasm' +data_dir = contents_dir + '/data' if os.path.exists(bundle_path): shutil.rmtree(bundle_path) @@ -47,6 +48,7 @@ os.mkdir(contents_dir) os.mkdir(exe_dir) os.mkdir(res_dir) os.mkdir(wasm_dir) +os.mkdir(data_dir) #----------------------------------------------------------- #NOTE: copy orca runtime executable and libraries diff --git a/sdk/io.c b/sdk/io.c new file mode 100644 index 0000000..90cf76a --- /dev/null +++ b/sdk/io.c @@ -0,0 +1,95 @@ +/************************************************************//** +* +* @file: io.c +* @author: Martin Fouilleul +* @date: 09/05/2023 +* +*****************************************************************/ +#include"io.h" +#include"io_stubs.c" + +//------------------------------------------------------------------------------ +// File stream read/write API +//------------------------------------------------------------------------------ + +file_handle file_open(str8 path, file_open_flags flags) +{ + io_req req = {.op = IO_OP_OPEN, + .size = path.len, + .buffer = path.ptr, + .openFlags = flags}; + + io_cmp cmp = io_wait_single_req(&req); + + //WARN: we always return a handle that can be queried for errors. Handles must be closed + // even if there was an error when opening + file_handle handle = { cmp.result }; + return(handle); +} + +void file_close(file_handle file) +{ + io_req req = {.op = IO_OP_CLOSE, + .handle = file}; + io_wait_single_req(&req); +} + +size_t file_size(file_handle file) +{ + io_req req = {.op = IO_OP_SIZE, + .handle = file}; + + io_cmp cmp = io_wait_single_req(&req); + return((size_t)cmp.result); +} + +size_t file_pos(file_handle file) +{ + io_req req = {.op = IO_OP_POS, + .handle = file}; + + io_cmp cmp = io_wait_single_req(&req); + return((size_t)cmp.result); +} + +size_t file_seek(file_handle file, long offset, file_whence whence) +{ + io_req req = {.op = IO_OP_SEEK, + .handle = file, + .size = offset, + .whence = whence}; + + io_cmp cmp = io_wait_single_req(&req); + return((size_t)cmp.result); +} + +size_t file_write(file_handle file, size_t size, char* buffer) +{ + io_req req = {.op = IO_OP_WRITE, + .handle = file, + .size = size, + .buffer = buffer}; + + io_cmp cmp = io_wait_single_req(&req); + return((size_t)cmp.result); +} + +size_t file_read(file_handle file, size_t size, char* buffer) +{ + io_req req = {.op = IO_OP_READ, + .handle = file, + .size = size, + .buffer = buffer}; + + io_cmp cmp = io_wait_single_req(&req); + return((size_t)cmp.result); +} + +int file_error(file_handle file) +{ + io_req req = {.op = IO_OP_ERROR, + .handle = file}; + + io_cmp cmp = io_wait_single_req(&req); + return((int)cmp.result); +} diff --git a/sdk/io.h b/sdk/io.h new file mode 100644 index 0000000..9e7dba5 --- /dev/null +++ b/sdk/io.h @@ -0,0 +1,28 @@ +/************************************************************//** +* +* @file: io.h +* @author: Martin Fouilleul +* @date: 09/05/2023 +* +*****************************************************************/ +#ifndef __IO_H_ +#define __IO_H_ + +#include"util/typedefs.h" +#include"util/strings.h" + +#include"io_common.h" + +file_handle file_open(str8 path, file_open_flags flags); +void file_close(file_handle file); + +size_t file_size(file_handle file); +size_t file_pos(file_handle file); +size_t file_seek(file_handle file, long offset, file_whence whence); + +size_t file_write(file_handle file, size_t size, char* buffer); +size_t file_read(file_handle file, size_t size, char* buffer); + +int file_error(file_handle file); + +#endif //__IO_H_ diff --git a/sdk/orca.c b/sdk/orca.c index 5608281..70d30b8 100644 --- a/sdk/orca.c +++ b/sdk/orca.c @@ -16,3 +16,5 @@ #include"graphics_common.c" #include"orca_surface.c" + +#include"io.c" diff --git a/sdk/orca.h b/sdk/orca.h index 696aba7..4d59ab4 100644 --- a/sdk/orca.h +++ b/sdk/orca.h @@ -16,5 +16,6 @@ #include"platform/platform_log.h" #include"platform/platform_assert.h" +#include"io.h" #endif //__ORCA_H_ diff --git a/src/io_api.json b/src/io_api.json new file mode 100644 index 0000000..fd9df10 --- /dev/null +++ b/src/io_api.json @@ -0,0 +1,9 @@ +[ +{ + "name": "io_wait_single_req", + "cname": "io_wait_single_req", + "ret": {"name": "io_cmp", "tag": "S"}, + "args": [ {"name": "req", + "type": {"name": "io_req*", "tag": "p"}}] +} +] diff --git a/src/io_common.h b/src/io_common.h new file mode 100644 index 0000000..e7c52c8 --- /dev/null +++ b/src/io_common.h @@ -0,0 +1,88 @@ +/************************************************************//** +* +* @file: io_common.h +* @author: Martin Fouilleul +* @date: 09/05/2023 +* +*****************************************************************/ +#ifndef __IO_COMMON_H_ +#define __IO_COMMON_H_ + +#include"util/typedefs.h" + +//------------------------------------------------------------------------------ +// Host IO interface +//------------------------------------------------------------------------------ + +typedef struct { u64 h; } file_handle; + +typedef u64 io_req_id; + +typedef u32 io_op; +enum +{ + IO_OP_OPEN, + IO_OP_CLOSE, + IO_OP_SIZE, + IO_OP_POS, + IO_OP_SEEK, + IO_OP_READ, + IO_OP_WRITE, + IO_OP_ERROR, + //... +}; + +typedef enum { FILE_SET, FILE_END, FILE_CURR } file_whence; + +typedef u32 file_open_flags; +enum { + IO_OPEN_READ, + IO_OPEN_WRITE, + IO_OPEN_APPEND, + IO_OPEN_TRUNCATE, + IO_OPEN_CREATE, + //... +}; + +typedef struct io_req +{ + io_req_id id; + io_op op; + file_handle handle; + u64 offset; + u64 size; + + union + { + char* buffer; + u64 shadow; // This is a horrible hack to get the same layout on wasm and on host + }; + + union + { + file_open_flags openFlags; + file_whence whence; + }; + +} io_req; + +typedef i32 io_error; +enum { + IO_OK = 0, + IO_ERR_INVALID, // invalid argument or argument combination + IO_ERR_PERM, // access denied + IO_ERR_PATH, // path does not exist + IO_ERR_EXISTS, // file already exists + IO_ERR_HANDLE, // invalid handle + IO_ERR_MAX_FILES, + //... +}; + +typedef struct io_cmp +{ + io_req id; + io_error error; + u64 result; +} io_cmp; + +#endif //__IO_COMMON_H_ diff --git a/src/io_impl.c b/src/io_impl.c new file mode 100644 index 0000000..27dbe29 --- /dev/null +++ b/src/io_impl.c @@ -0,0 +1,247 @@ +/************************************************************//** +* +* @file: io_impl.c +* @author: Martin Fouilleul +* @date: 09/05/2023 +* +*****************************************************************/ + +#include +#include +#include"io_common.h" + +//TODO: - file_handle to FILE* association +// - open / close handles +// - read / write +// - file positioning & size + +typedef struct file_slot +{ + u32 generation; + int fd; + io_error error; + list_elt freeListElt; + +} file_slot; + +enum +{ + ORCA_MAX_FILE_SLOTS = 256, +}; + +typedef struct file_table +{ + file_slot slots[ORCA_MAX_FILE_SLOTS]; + u32 nextSlot; + list_info freeList; +} file_table; + +file_table __globalFileTable = {0}; + +file_slot* file_slot_alloc(file_table* table) +{ + file_slot* slot = list_pop_entry(&table->freeList, file_slot, freeListElt); + if(!slot && table->nextSlot < ORCA_MAX_FILE_SLOTS) + { + slot = &table->slots[table->nextSlot]; + slot->generation = 1; + table->nextSlot++; + } + return(slot); +} + +void file_slot_recycle(file_table* table, file_slot* slot) +{ + slot->generation++; + list_push(&table->freeList, &slot->freeListElt); +} + +file_handle file_handle_from_slot(file_table* table, file_slot* slot) +{ + u64 index = slot - table->slots; + u64 generation = slot->generation; + file_handle handle = {.h = (generation<<32) | index }; + return(handle); +} + +file_slot* file_slot_from_handle(file_table* table, file_handle handle) +{ + file_slot* slot = 0; + + u64 index = handle.h & 0xffffffff; + u64 generation = handle.h>>32; + + if(index < ORCA_MAX_FILE_SLOTS) + { + file_slot* candidate = &table->slots[index]; + if(candidate->generation == generation) + { + slot = candidate; + } + } + return(slot); +} + +io_cmp io_open(io_req* req) +{ + io_cmp cmp = {0}; + + int flags = 0; + if(req->openFlags & IO_OPEN_READ) + { + if(req->openFlags & IO_OPEN_WRITE) + { + flags = O_RDWR; + } + else + { + flags = O_RDONLY; + } + } + else if(req->openFlags & IO_OPEN_WRITE) + { + flags = O_WRONLY; + } + + if(req->openFlags & IO_OPEN_TRUNCATE) + { + flags |= O_TRUNC; + } + if(req->openFlags & IO_OPEN_APPEND) + { + flags |= O_APPEND; + } + if(req->openFlags & IO_OPEN_CREATE) + { + flags |= O_CREAT; + } + + mode_t mode = S_IRUSR + | S_IWUSR + | S_IRGRP + | S_IWGRP + | S_IROTH + | S_IWOTH; + + //NOTE: build path + //////////////////////////////////////////////////////////////////////////////////// + //TODO: canonicalize directory path & check that it's inside local app folder + //////////////////////////////////////////////////////////////////////////////////// + + mem_arena* scratch = mem_scratch(); + mem_arena_marker mark = mem_arena_mark(scratch); + + str8_list list = {0}; + + str8 execPath = mp_app_get_executable_path(scratch); + str8 execDir = mp_path_directory(execPath); + + str8_list_push(scratch, &list, execDir); + str8_list_push(scratch, &list, STR8("/../data/")); + str8_list_push(scratch, &list, str8_from_buffer(req->size, req->buffer)); + + str8 absPath = str8_list_join(scratch, list); + char* absCString = str8_to_cstring(scratch, absPath); + + //NOTE: open + int fd = open(absCString, flags, mode); + + if(fd >= 0) + { + file_slot* slot = file_slot_alloc(&__globalFileTable); + if(slot) + { + slot->fd = fd; + file_handle handle = file_handle_from_slot(&__globalFileTable, slot); + cmp.result = handle.h; + } + else + { + cmp.error = IO_ERR_MAX_FILES; + close(fd); + } + } + else + { + switch(errno) + { + case EACCES: + cmp.error = IO_ERR_PERM; + break; + + //TODO: convert open error codes to io_error + default: + cmp.error = IO_ERR_INVALID; + } + } + + mem_arena_clear_to(scratch, mark); + + return(cmp); +} + +io_cmp io_close(io_req* req) +{ + io_cmp cmp = {0}; + file_slot* slot = file_slot_from_handle(&__globalFileTable, req->handle); + if(slot) + { + close(slot->fd); + file_slot_recycle(&__globalFileTable, slot); + } + else + { + cmp.error = IO_ERR_HANDLE; + } + return(cmp); +} + + +io_cmp io_wait_single_req(io_req* req) +{ + mem_arena* scratch = mem_scratch(); + + io_cmp cmp = {0}; + + //////////////////////////////////////////////////////////////////////////////////////// + //TODO: we need to convert the req->buffer wasm pointer to a native pointer here + //////////////////////////////////////////////////////////////////////////////////////// + //TODO: check that req->buffer lies in wasm memory with at least req->size space + + ///////////////////////////////////////////////// + //TODO: we can't read reqs from wasm memory since the pointers are not the same size + // (hence the layout won't be the same...) + ///////////////////////////////////////////////// + + //NOTE: for some reason, wasm3 memory doesn't start at the beginning of the block we give it. + u32 memSize = 0; + char* memory = (char*)m3_GetMemory(__orcaApp.runtime.m3Runtime, &memSize, 0); + + u64 bufferIndex = (u64)req->buffer & 0xffffff; + + if(bufferIndex + req->size > memSize) + { + cmp.error = IO_ERR_INVALID; + } + else + { + req->buffer = memory + bufferIndex; + + switch(req->op) + { + case IO_OP_OPEN: + cmp = io_open(req); + break; + + case IO_OP_CLOSE: + cmp = io_close(req); + break; + + default: + cmp.error = IO_ERR_INVALID; + break; + } + } + + return(cmp); +} diff --git a/src/main.c b/src/main.c index 2fb4178..2669f9e 100644 --- a/src/main.c +++ b/src/main.c @@ -137,6 +137,8 @@ typedef struct orca_app orca_app __orcaApp = {0}; +#include"io_impl.c" + void orca_log(log_level level, int fileLen, char* file, @@ -348,10 +350,11 @@ orca_runtime* orca_runtime_get() #include"bindgen_core_api.c" #include"canvas_api_bind.c" +#include"io_api_bind_gen.c" + #include"bindgen_gles_api.c" #include"manual_gles_api.c" - void* orca_runloop(void* user) { orca_app* app = &__orcaApp; @@ -396,6 +399,7 @@ void* orca_runloop(void* user) //NOTE: bind orca APIs bindgen_link_core_api(app->runtime.m3Module); bindgen_link_canvas_api(app->runtime.m3Module); + bindgen_link_io_api(app->runtime.m3Module); bindgen_link_gles_api(app->runtime.m3Module); manual_link_gles_api(app->runtime.m3Module);