[file io, wip] open/close file API

This commit is contained in:
Martin Fouilleul 2023-05-09 18:44:23 +02:00
parent 0fab5635e2
commit 4ae51d7a23
12 changed files with 488 additions and 4 deletions

View File

@ -29,7 +29,7 @@ elif [ $target = wasm3 ] ; then
for file in ./ext/wasm3/source/*.c ; do for file in ./ext/wasm3/source/*.c ; do
name=$(basename $file) name=$(basename $file)
name=${name/.c/.o} 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 done
ar -rcs ./bin/libwasm3.a ./bin/obj/*.o ar -rcs ./bin/libwasm3.a ./bin/obj/*.o
rm -rf ./bin/obj rm -rf ./bin/obj
@ -43,7 +43,7 @@ elif [ $target = orca ] ; then
cp milepost/bin/libGLESv2.dylib bin/ cp milepost/bin/libGLESv2.dylib bin/
cp milepost/bin/libEGL.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" LIBS="-Lbin -lmilepost -lwasm3"
FLAGS="-g -DLOG_COMPILE_DEBUG -mmacos-version-min=10.15.4 -maes" FLAGS="-g -DLOG_COMPILE_DEBUG -mmacos-version-min=10.15.4 -maes"
@ -57,6 +57,11 @@ elif [ $target = orca ] ; then
--guest-include graphics.h \ --guest-include graphics.h \
--wasm3-bindings ./src/canvas_api_bind_gen.c --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 # compile orca
clang $FLAGS $INCLUDES $LIBS -o bin/orca src/main.c clang $FLAGS $INCLUDES $LIBS -o bin/orca src/main.c

View File

@ -8,7 +8,7 @@ wasmFlags="--target=wasm32 \
-Wl,--allow-undefined \ -Wl,--allow-undefined \
-g \ -g \
-D__ORCA__ \ -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 /usr/local/opt/llvm/bin/clang $wasmFlags -o ./module.wasm ../../sdk/orca.c src/main.c

View File

@ -40,6 +40,9 @@ void OnInit(void)
//TODO create surface for main window //TODO create surface for main window
surface = mg_surface_main(); surface = mg_surface_main();
canvas = mg_canvas_create(); 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) void OnFrameResize(u32 width, u32 height)

View File

@ -39,6 +39,7 @@ contents_dir = bundle_path + '/Contents'
exe_dir = contents_dir + '/MacOS' exe_dir = contents_dir + '/MacOS'
res_dir = contents_dir + '/resources' res_dir = contents_dir + '/resources'
wasm_dir = contents_dir + '/wasm' wasm_dir = contents_dir + '/wasm'
data_dir = contents_dir + '/data'
if os.path.exists(bundle_path): if os.path.exists(bundle_path):
shutil.rmtree(bundle_path) shutil.rmtree(bundle_path)
@ -47,6 +48,7 @@ os.mkdir(contents_dir)
os.mkdir(exe_dir) os.mkdir(exe_dir)
os.mkdir(res_dir) os.mkdir(res_dir)
os.mkdir(wasm_dir) os.mkdir(wasm_dir)
os.mkdir(data_dir)
#----------------------------------------------------------- #-----------------------------------------------------------
#NOTE: copy orca runtime executable and libraries #NOTE: copy orca runtime executable and libraries

95
sdk/io.c Normal file
View File

@ -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);
}

28
sdk/io.h Normal file
View File

@ -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_

View File

@ -16,3 +16,5 @@
#include"graphics_common.c" #include"graphics_common.c"
#include"orca_surface.c" #include"orca_surface.c"
#include"io.c"

View File

@ -16,5 +16,6 @@
#include"platform/platform_log.h" #include"platform/platform_log.h"
#include"platform/platform_assert.h" #include"platform/platform_assert.h"
#include"io.h"
#endif //__ORCA_H_ #endif //__ORCA_H_

9
src/io_api.json Normal file
View File

@ -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"}}]
}
]

88
src/io_common.h Normal file
View File

@ -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_

247
src/io_impl.c Normal file
View File

@ -0,0 +1,247 @@
/************************************************************//**
*
* @file: io_impl.c
* @author: Martin Fouilleul
* @date: 09/05/2023
*
*****************************************************************/
#include<fcntl.h>
#include<unistd.h>
#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);
}

View File

@ -137,6 +137,8 @@ typedef struct orca_app
orca_app __orcaApp = {0}; orca_app __orcaApp = {0};
#include"io_impl.c"
void orca_log(log_level level, void orca_log(log_level level,
int fileLen, int fileLen,
char* file, char* file,
@ -348,10 +350,11 @@ orca_runtime* orca_runtime_get()
#include"bindgen_core_api.c" #include"bindgen_core_api.c"
#include"canvas_api_bind.c" #include"canvas_api_bind.c"
#include"io_api_bind_gen.c"
#include"bindgen_gles_api.c" #include"bindgen_gles_api.c"
#include"manual_gles_api.c" #include"manual_gles_api.c"
void* orca_runloop(void* user) void* orca_runloop(void* user)
{ {
orca_app* app = &__orcaApp; orca_app* app = &__orcaApp;
@ -396,6 +399,7 @@ void* orca_runloop(void* user)
//NOTE: bind orca APIs //NOTE: bind orca APIs
bindgen_link_core_api(app->runtime.m3Module); bindgen_link_core_api(app->runtime.m3Module);
bindgen_link_canvas_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); bindgen_link_gles_api(app->runtime.m3Module);
manual_link_gles_api(app->runtime.m3Module); manual_link_gles_api(app->runtime.m3Module);