re-integrating file IO with common platform API
This commit is contained in:
parent
02014a6ddf
commit
a4a3423907
2
milepost
2
milepost
|
@ -1 +1 @@
|
||||||
Subproject commit 02bfe587c882ecae0ac7fba22f48808113d4f93d
|
Subproject commit c041b212ab3c3f51d7cab6bc341e6c0e6d033962
|
|
@ -78,12 +78,12 @@ void OnInit(void)
|
||||||
#endif // TEST_IMAGE
|
#endif // TEST_IMAGE
|
||||||
|
|
||||||
//NOTE: testing file io
|
//NOTE: testing file io
|
||||||
file_handle file = file_open(STR8("/test_write.txt"), IO_OPEN_CREATE | IO_OPEN_WRITE);
|
file_handle file = file_open(STR8("./test_write.txt"), FILE_OPEN_CREATE | FILE_OPEN_WRITE);
|
||||||
|
|
||||||
str8 string = STR8("Hello, file!\n");
|
str8 string = STR8("Hello, file!\n");
|
||||||
file_write(file, string.len, string.ptr);
|
file_write(file, string.len, string.ptr);
|
||||||
file_close(file);
|
file_close(file);
|
||||||
|
/*
|
||||||
file = file_open(STR8("/dir1/test_read.txt"), IO_OPEN_READ);
|
file = file_open(STR8("/dir1/test_read.txt"), IO_OPEN_READ);
|
||||||
u64 size = file_size(file);
|
u64 size = file_size(file);
|
||||||
char* buffer = mem_arena_alloc(mem_scratch(), size);
|
char* buffer = mem_arena_alloc(mem_scratch(), size);
|
||||||
|
@ -91,6 +91,7 @@ void OnInit(void)
|
||||||
file_close(file);
|
file_close(file);
|
||||||
|
|
||||||
log_info("read file: %.*s", (int)size, buffer);
|
log_info("read file: %.*s", (int)size, buffer);
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
void OnFrameResize(u32 width, u32 height)
|
void OnFrameResize(u32 width, u32 height)
|
||||||
|
|
95
sdk/io.c
95
sdk/io.c
|
@ -1,95 +0,0 @@
|
||||||
/************************************************************//**
|
|
||||||
*
|
|
||||||
* @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
28
sdk/io.h
|
@ -1,28 +0,0 @@
|
||||||
/************************************************************//**
|
|
||||||
*
|
|
||||||
* @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_
|
|
|
@ -9,6 +9,7 @@
|
||||||
#include"platform/orca_memory.c"
|
#include"platform/orca_memory.c"
|
||||||
#include"platform/orca_strings.c"
|
#include"platform/orca_strings.c"
|
||||||
#include"platform/orca_log.c"
|
#include"platform/orca_log.c"
|
||||||
|
#include"platform/platform_io_common.c"
|
||||||
|
|
||||||
#include"util/memory.c"
|
#include"util/memory.c"
|
||||||
#include"util/strings.c"
|
#include"util/strings.c"
|
||||||
|
@ -17,4 +18,4 @@
|
||||||
#include"graphics_common.c"
|
#include"graphics_common.c"
|
||||||
#include"orca_surface.c"
|
#include"orca_surface.c"
|
||||||
|
|
||||||
#include"io.c"
|
#include"io_stubs.c"
|
||||||
|
|
|
@ -15,7 +15,6 @@
|
||||||
#include"util/utf8.h"
|
#include"util/utf8.h"
|
||||||
#include"platform/platform_log.h"
|
#include"platform/platform_log.h"
|
||||||
#include"platform/platform_assert.h"
|
#include"platform/platform_assert.h"
|
||||||
|
#include"platform/platform_io.h"
|
||||||
#include"io.h"
|
|
||||||
|
|
||||||
#endif //__ORCA_H_
|
#endif //__ORCA_H_
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"name": "io_wait_single_req",
|
"name": "io_wait_single_req",
|
||||||
"cname": "io_wait_single_req",
|
"cname": "orca_io_wait_single_req",
|
||||||
"ret": {"name": "io_cmp", "tag": "S"},
|
"ret": {"name": "io_cmp", "tag": "S"},
|
||||||
"args": [ {"name": "req",
|
"args": [ {"name": "req",
|
||||||
"type": {"name": "io_req*", "tag": "p"}}]
|
"type": {"name": "io_req*", "tag": "p"}}]
|
||||||
|
|
105
src/io_common.h
105
src/io_common.h
|
@ -1,105 +0,0 @@
|
||||||
/************************************************************//**
|
|
||||||
*
|
|
||||||
* @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 { IO_SEEK_SET, IO_SEEK_END, IO_SEEK_CURRENT } 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_UNKNOWN,
|
|
||||||
IO_ERR_OP, // unsupported operation
|
|
||||||
IO_ERR_HANDLE, // invalid handle
|
|
||||||
IO_ERR_PREV, // previously had a fatal error (last error stored on handle)
|
|
||||||
IO_ERR_ARG, // invalid argument or argument combination
|
|
||||||
IO_ERR_PERM, // access denied
|
|
||||||
IO_ERR_SPACE, // no space left
|
|
||||||
IO_ERR_NO_ENTRY, // file or directory does not exist
|
|
||||||
IO_ERR_EXISTS, // file already exists
|
|
||||||
IO_ERR_NOT_DIR, // path element is not a directory
|
|
||||||
IO_ERR_DIR, // attempted to write directory
|
|
||||||
IO_ERR_MAX_FILES, // max open files reached
|
|
||||||
IO_ERR_MAX_LINKS, // too many symbolic links in path
|
|
||||||
IO_ERR_PATH_LENGTH, // path too long
|
|
||||||
IO_ERR_FILE_SIZE, // file too big
|
|
||||||
IO_ERR_OVERFLOW, // offset too big
|
|
||||||
IO_ERR_NOT_READY, // no data ready to be read/written
|
|
||||||
IO_ERR_MEM, // failed to allocate memory
|
|
||||||
IO_ERR_INTERRUPT, // operation interrupted by a signal
|
|
||||||
IO_ERR_PHYSICAL, // physical IO error
|
|
||||||
IO_ERR_NO_DEVICE, // device not found
|
|
||||||
IO_ERR_WALKOUT, // attempted to walk out of root directory
|
|
||||||
|
|
||||||
//...
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef struct io_cmp
|
|
||||||
{
|
|
||||||
io_req id;
|
|
||||||
io_error error;
|
|
||||||
u64 result;
|
|
||||||
} io_cmp;
|
|
||||||
|
|
||||||
#endif //__IO_COMMON_H_
|
|
716
src/io_impl.c
716
src/io_impl.c
|
@ -5,722 +5,42 @@
|
||||||
* @date: 09/05/2023
|
* @date: 09/05/2023
|
||||||
*
|
*
|
||||||
*****************************************************************/
|
*****************************************************************/
|
||||||
|
#include"platform/platform_io.h"
|
||||||
|
|
||||||
#include<fcntl.h>
|
io_cmp orca_io_wait_single_req(io_req* wasmReq)
|
||||||
#include<sys/stat.h>
|
|
||||||
#include<unistd.h>
|
|
||||||
#include<limits.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;
|
|
||||||
bool fatal;
|
|
||||||
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_error io_convert_errno(int e)
|
|
||||||
{
|
|
||||||
io_error error;
|
|
||||||
switch(e)
|
|
||||||
{
|
|
||||||
case EPERM:
|
|
||||||
case EACCES:
|
|
||||||
case EROFS:
|
|
||||||
error = IO_ERR_PERM;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ENOENT:
|
|
||||||
error = IO_ERR_NO_ENTRY;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EINTR:
|
|
||||||
error = IO_ERR_INTERRUPT;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EIO:
|
|
||||||
error = IO_ERR_PHYSICAL;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ENXIO:
|
|
||||||
error = IO_ERR_NO_DEVICE;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EBADF:
|
|
||||||
// this should only happen when user tries to write/read to a file handle
|
|
||||||
// opened with readonly/writeonly access
|
|
||||||
error = IO_ERR_PERM;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ENOMEM:
|
|
||||||
error = IO_ERR_MEM;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EFAULT:
|
|
||||||
case EINVAL:
|
|
||||||
case EDOM:
|
|
||||||
error = IO_ERR_ARG;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EBUSY:
|
|
||||||
case EAGAIN:
|
|
||||||
error = IO_ERR_NOT_READY;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EEXIST:
|
|
||||||
error = IO_ERR_EXISTS;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ENOTDIR:
|
|
||||||
error = IO_ERR_NOT_DIR;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EISDIR:
|
|
||||||
error = IO_ERR_DIR;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ENFILE:
|
|
||||||
case EMFILE:
|
|
||||||
error = IO_ERR_MAX_FILES;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EFBIG:
|
|
||||||
error = IO_ERR_FILE_SIZE;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ENOSPC:
|
|
||||||
case EDQUOT:
|
|
||||||
error = IO_ERR_SPACE;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ELOOP:
|
|
||||||
error = IO_ERR_MAX_LINKS;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ENAMETOOLONG:
|
|
||||||
error = IO_ERR_PATH_LENGTH;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EOVERFLOW:
|
|
||||||
error = IO_ERR_OVERFLOW;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
error = IO_ERR_UNKNOWN;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
return(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
typedef struct io_path_walk_result
|
|
||||||
{
|
|
||||||
io_error error;
|
|
||||||
int fd;
|
|
||||||
|
|
||||||
} io_path_walk_result;
|
|
||||||
|
|
||||||
io_path_walk_result io_path_walk(str8 path)
|
|
||||||
{
|
|
||||||
io_path_walk_result result = {.fd = -1};
|
|
||||||
|
|
||||||
mem_arena* scratch = mem_scratch();
|
|
||||||
mem_arena_marker mark = mem_arena_mark(scratch);
|
|
||||||
|
|
||||||
str8_list sep = {0};
|
|
||||||
str8_list_push(scratch, &sep, STR8("/"));
|
|
||||||
str8_list pathElements = str8_split(scratch, path, sep);
|
|
||||||
|
|
||||||
str8 execPath = path_slice_directory(path_find_executable(scratch));
|
|
||||||
str8_list list = {0};
|
|
||||||
str8_list_push(scratch, &list, execPath);
|
|
||||||
str8_list_push(scratch, &list, STR8("../app/data"));
|
|
||||||
str8 dataPath = str8_list_join(scratch, list);
|
|
||||||
|
|
||||||
result.fd = open(str8_to_cstring(scratch, dataPath), O_RDONLY);
|
|
||||||
if(result.fd < 0)
|
|
||||||
{
|
|
||||||
result.error = io_convert_errno(errno);
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
struct stat dirStat;
|
|
||||||
if(fstat(result.fd, &dirStat))
|
|
||||||
{
|
|
||||||
result.error = io_convert_errno(errno);
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
ino_t baseInode = dirStat.st_ino;
|
|
||||||
ino_t currentInode = baseInode;
|
|
||||||
|
|
||||||
for_list(&pathElements.list, elt, str8_elt, listElt)
|
|
||||||
{
|
|
||||||
str8 name = elt->string;
|
|
||||||
|
|
||||||
if(!str8_cmp(name, STR8(".")))
|
|
||||||
{
|
|
||||||
//NOTE: skip
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else if(!str8_cmp(name, STR8("..")))
|
|
||||||
{
|
|
||||||
//NOTE: check that we don't escape 'root' dir
|
|
||||||
if(currentInode == baseInode)
|
|
||||||
{
|
|
||||||
result.error = IO_ERR_WALKOUT;
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// open that dir and continue
|
|
||||||
int nextFd = openat(result.fd, "..", O_RDONLY);
|
|
||||||
if(!nextFd)
|
|
||||||
{
|
|
||||||
result.error = io_convert_errno(errno);
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
close(result.fd);
|
|
||||||
result.fd = nextFd;
|
|
||||||
if(fstat(result.fd, &dirStat))
|
|
||||||
{
|
|
||||||
result.error = io_convert_errno(errno);
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
currentInode = dirStat.st_ino;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
char* nameCStr = str8_to_cstring(scratch, name);
|
|
||||||
struct stat st;
|
|
||||||
|
|
||||||
if(fstatat(result.fd, nameCStr, &st, AT_SYMLINK_NOFOLLOW))
|
|
||||||
{
|
|
||||||
if(&elt->listElt != list_last(&pathElements.list))
|
|
||||||
{
|
|
||||||
result.error = io_convert_errno(errno);
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int type = st.st_mode & S_IFMT;
|
|
||||||
|
|
||||||
if(type == S_IFLNK)
|
|
||||||
{
|
|
||||||
// symlink, check that it's relative, and insert it in elements
|
|
||||||
char buff[PATH_MAX+1];
|
|
||||||
int r = readlinkat(result.fd, nameCStr, buff, PATH_MAX);
|
|
||||||
if(r<0)
|
|
||||||
{
|
|
||||||
result.error = io_convert_errno(errno);
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
else if(r == 0)
|
|
||||||
{
|
|
||||||
//NOTE: skip
|
|
||||||
}
|
|
||||||
else if(buff[0] == '/')
|
|
||||||
{
|
|
||||||
result.error = IO_ERR_WALKOUT;
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
buff[r] = '\0';
|
|
||||||
|
|
||||||
str8_list linkElements = str8_split(scratch, str8_from_buffer(r, buff), sep);
|
|
||||||
if(!list_empty(&linkElements.list))
|
|
||||||
{
|
|
||||||
//NOTE: insert linkElements into pathElements after elt
|
|
||||||
list_elt* tmp = elt->listElt.next;
|
|
||||||
elt->listElt.next = linkElements.list.first;
|
|
||||||
linkElements.list.last->next = tmp;
|
|
||||||
if(!tmp)
|
|
||||||
{
|
|
||||||
pathElements.list.last = linkElements.list.last;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(type == S_IFDIR)
|
|
||||||
{
|
|
||||||
// dir, open it and continue
|
|
||||||
int nextFd = openat(result.fd, nameCStr, O_RDONLY);
|
|
||||||
if(!nextFd)
|
|
||||||
{
|
|
||||||
result.error = io_convert_errno(errno);
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
close(result.fd);
|
|
||||||
result.fd = nextFd;
|
|
||||||
currentInode = st.st_ino;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if(type == S_IFREG)
|
|
||||||
{
|
|
||||||
// regular file, check that we're at the last element
|
|
||||||
if(&elt->listElt != list_last(&pathElements.list))
|
|
||||||
{
|
|
||||||
result.error = IO_ERR_NOT_DIR;
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
result.error = IO_ERR_NOT_DIR;
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
goto end;
|
|
||||||
|
|
||||||
error:
|
|
||||||
{
|
|
||||||
close(result.fd);
|
|
||||||
result.fd = -1;
|
|
||||||
}
|
|
||||||
end:
|
|
||||||
mem_arena_clear_to(scratch, mark);
|
|
||||||
return(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
io_cmp io_open(io_req* req)
|
|
||||||
{
|
|
||||||
io_cmp cmp = {0};
|
|
||||||
|
|
||||||
file_slot* slot = file_slot_alloc(&__globalFileTable);
|
|
||||||
if(!slot)
|
|
||||||
{
|
|
||||||
cmp.error = IO_ERR_MAX_FILES;
|
|
||||||
cmp.result = 0;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
file_handle handle = file_handle_from_slot(&__globalFileTable, slot);
|
|
||||||
cmp.result = handle.h;
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
flags |= O_NOFOLLOW;
|
|
||||||
|
|
||||||
mode_t mode = S_IRUSR
|
|
||||||
| S_IWUSR
|
|
||||||
| S_IRGRP
|
|
||||||
| S_IWGRP
|
|
||||||
| S_IROTH
|
|
||||||
| S_IWOTH;
|
|
||||||
|
|
||||||
//NOTE: walk path (ensuring it's inside app's data directory subtree)
|
|
||||||
str8 path = str8_from_buffer(req->size, req->buffer);
|
|
||||||
io_path_walk_result walkRes = io_path_walk(path);
|
|
||||||
if(walkRes.error != IO_OK)
|
|
||||||
{
|
|
||||||
slot->fd = -1;
|
|
||||||
slot->fatal = true;
|
|
||||||
slot->error = walkRes.error;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
//NOTE: open
|
|
||||||
mem_arena* scratch = mem_scratch();
|
|
||||||
mem_arena_marker mark = mem_arena_mark(scratch);
|
|
||||||
|
|
||||||
str8 name = path_slice_filename(path);
|
|
||||||
char* nameCStr = str8_to_cstring(scratch, name);
|
|
||||||
int fd = openat(walkRes.fd, nameCStr, flags, mode);
|
|
||||||
close(walkRes.fd);
|
|
||||||
|
|
||||||
mem_arena_clear_to(scratch, mark);
|
|
||||||
|
|
||||||
if(fd >= 0)
|
|
||||||
{
|
|
||||||
slot->fd = fd;
|
|
||||||
file_handle handle = file_handle_from_slot(&__globalFileTable, slot);
|
|
||||||
cmp.result = handle.h;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
slot->fd = -1;
|
|
||||||
slot->fatal = true;
|
|
||||||
slot->error = io_convert_errno(errno);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cmp.error = slot->error;
|
|
||||||
}
|
|
||||||
|
|
||||||
return(cmp);
|
|
||||||
}
|
|
||||||
|
|
||||||
io_cmp io_close(file_slot* slot, io_req* req)
|
|
||||||
{
|
|
||||||
io_cmp cmp = {0};
|
|
||||||
if(slot->fd >= 0)
|
|
||||||
{
|
|
||||||
close(slot->fd);
|
|
||||||
}
|
|
||||||
file_slot_recycle(&__globalFileTable, slot);
|
|
||||||
return(cmp);
|
|
||||||
}
|
|
||||||
|
|
||||||
io_cmp io_size(file_slot* slot, io_req* req)
|
|
||||||
{
|
|
||||||
io_cmp cmp = {0};
|
|
||||||
|
|
||||||
struct stat s;
|
|
||||||
if(fstat(slot->fd, &s))
|
|
||||||
{
|
|
||||||
slot->error = IO_ERR_UNKNOWN;
|
|
||||||
cmp.error = slot->error;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
cmp.result = s.st_size;
|
|
||||||
}
|
|
||||||
|
|
||||||
return(cmp);
|
|
||||||
}
|
|
||||||
|
|
||||||
io_cmp io_pos(file_slot* slot, io_req* req)
|
|
||||||
{
|
|
||||||
io_cmp cmp = {0};
|
|
||||||
cmp.result = lseek(slot->fd, 0, SEEK_CUR);
|
|
||||||
if(cmp.result < 0)
|
|
||||||
{
|
|
||||||
switch(errno)
|
|
||||||
{
|
|
||||||
case EINVAL:
|
|
||||||
slot->error = IO_ERR_ARG;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EOVERFLOW:
|
|
||||||
slot->error = IO_ERR_OVERFLOW;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
slot->error = IO_ERR_UNKNOWN;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
cmp.error = slot->error;
|
|
||||||
}
|
|
||||||
|
|
||||||
return(cmp);
|
|
||||||
}
|
|
||||||
|
|
||||||
io_cmp io_seek(file_slot* slot, io_req* req)
|
|
||||||
{
|
|
||||||
io_cmp cmp = {0};
|
|
||||||
|
|
||||||
int whence;
|
|
||||||
switch(req->whence)
|
|
||||||
{
|
|
||||||
case IO_SEEK_CURRENT:
|
|
||||||
whence = SEEK_CUR;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case IO_SEEK_SET:
|
|
||||||
whence = SEEK_SET;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case IO_SEEK_END:
|
|
||||||
whence = SEEK_END;
|
|
||||||
}
|
|
||||||
cmp.result = lseek(slot->fd, req->size, whence);
|
|
||||||
|
|
||||||
if(cmp.result < 0)
|
|
||||||
{
|
|
||||||
switch(errno)
|
|
||||||
{
|
|
||||||
case EINVAL:
|
|
||||||
slot->error = IO_ERR_ARG;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EOVERFLOW:
|
|
||||||
slot->error = IO_ERR_OVERFLOW;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
slot->error = IO_ERR_UNKNOWN;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
cmp.error = slot->error;
|
|
||||||
}
|
|
||||||
|
|
||||||
return(cmp);
|
|
||||||
}
|
|
||||||
|
|
||||||
io_cmp io_read(file_slot* slot, io_req* req)
|
|
||||||
{
|
|
||||||
io_cmp cmp = {0};
|
|
||||||
|
|
||||||
cmp.result = read(slot->fd, req->buffer, req->size);
|
|
||||||
|
|
||||||
if(cmp.result < 0)
|
|
||||||
{
|
|
||||||
switch(errno)
|
|
||||||
{
|
|
||||||
case EAGAIN:
|
|
||||||
slot->error = IO_ERR_NOT_READY;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EFAULT:
|
|
||||||
case EINVAL:
|
|
||||||
slot->error = IO_ERR_ARG;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ENOBUFS:
|
|
||||||
case ENOMEM:
|
|
||||||
slot->error = IO_ERR_MEM;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
slot->error = IO_ERR_UNKNOWN;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
cmp.error = slot->error;
|
|
||||||
}
|
|
||||||
|
|
||||||
return(cmp);
|
|
||||||
}
|
|
||||||
|
|
||||||
io_cmp io_write(file_slot* slot, io_req* req)
|
|
||||||
{
|
|
||||||
io_cmp cmp = {0};
|
|
||||||
|
|
||||||
cmp.result = write(slot->fd, req->buffer, req->size);
|
|
||||||
|
|
||||||
if(cmp.result < 0)
|
|
||||||
{
|
|
||||||
switch(errno)
|
|
||||||
{
|
|
||||||
case EDQUOT:
|
|
||||||
case ENOSPC:
|
|
||||||
slot->error = IO_ERR_SPACE;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EFAULT:
|
|
||||||
case EINVAL:
|
|
||||||
slot->error = IO_ERR_ARG;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EAGAIN:
|
|
||||||
slot->error = IO_ERR_NOT_READY;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case EFBIG:
|
|
||||||
slot->error = IO_ERR_FILE_SIZE;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ENOBUFS:
|
|
||||||
case ENOMEM:
|
|
||||||
slot->error = IO_ERR_MEM;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
slot->error = IO_ERR_UNKNOWN;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
cmp.error = slot->error;
|
|
||||||
}
|
|
||||||
|
|
||||||
return(cmp);
|
|
||||||
}
|
|
||||||
|
|
||||||
io_cmp io_get_error(file_slot* slot, io_req* req)
|
|
||||||
{
|
|
||||||
io_cmp cmp = {0};
|
|
||||||
cmp.result = slot->error;
|
|
||||||
return(cmp);
|
|
||||||
}
|
|
||||||
|
|
||||||
io_cmp io_wait_single_req(io_req* req)
|
|
||||||
{
|
{
|
||||||
mem_arena* scratch = mem_scratch();
|
mem_arena* scratch = mem_scratch();
|
||||||
|
|
||||||
io_cmp cmp = {0};
|
io_cmp cmp = {0};
|
||||||
|
io_req req = *wasmReq;
|
||||||
////////////////////////////////////////////////////////////////////////////////////////
|
//NOTE: convert the req->buffer wasm pointer to a native pointer
|
||||||
//TODO: we need to convert the req->buffer wasm pointer to a native pointer here
|
// for some reason, wasm3 memory doesn't start at the beginning of the block we give it.
|
||||||
////////////////////////////////////////////////////////////////////////////////////////
|
u64 bufferIndex = (u64)req.buffer & 0xffffffff;
|
||||||
//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;
|
u32 memSize = 0;
|
||||||
char* memory = (char*)m3_GetMemory(__orcaApp.runtime.m3Runtime, &memSize, 0);
|
char* memory = (char*)m3_GetMemory(__orcaApp.runtime.m3Runtime, &memSize, 0);
|
||||||
|
|
||||||
u64 bufferIndex = (u64)req->buffer & 0xffffffff;
|
if(bufferIndex + req.size > memSize)
|
||||||
|
|
||||||
if(bufferIndex + req->size > memSize)
|
|
||||||
{
|
{
|
||||||
cmp.error = IO_ERR_ARG;
|
cmp.error = IO_ERR_ARG;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
//TODO: avoid modifying req.
|
req.buffer = memory + bufferIndex;
|
||||||
req->buffer = memory + bufferIndex;
|
|
||||||
|
|
||||||
file_slot* slot = 0;
|
//TODO: do some further ownership/rights checking here, and make sure we modify flags to avoid walking out the app folder
|
||||||
if(req->op != IO_OP_OPEN)
|
|
||||||
|
if(req.op == IO_OP_OPEN_AT)
|
||||||
{
|
{
|
||||||
slot = file_slot_from_handle(&__globalFileTable, req->handle);
|
////////////////////////////////////////////////////////////////////////
|
||||||
if(!slot)
|
//TODO: should change root to app local folder
|
||||||
{
|
// - if file handle is null, set it to pre-opened handle to app local folder
|
||||||
cmp.error = IO_ERR_HANDLE;
|
// - if file handle is not null, check that it is valid
|
||||||
}
|
// --> this means we probably need a second indirection: from wasm file handle to native file handle
|
||||||
else if(slot->fatal && req->op != IO_OP_CLOSE)
|
////////////////////////////////////////////////////////////////////////
|
||||||
{
|
|
||||||
cmp.error = IO_ERR_PREV;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(cmp.error == IO_OK)
|
cmp = io_wait_single_req(&req);
|
||||||
{
|
|
||||||
switch(req->op)
|
|
||||||
{
|
|
||||||
case IO_OP_OPEN:
|
|
||||||
cmp = io_open(req);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case IO_OP_CLOSE:
|
|
||||||
cmp = io_close(slot, req);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case IO_OP_SIZE:
|
|
||||||
cmp = io_size(slot, req);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case IO_OP_READ:
|
|
||||||
cmp = io_read(slot, req);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case IO_OP_WRITE:
|
|
||||||
cmp = io_write(slot, req);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case IO_OP_POS:
|
|
||||||
cmp = io_pos(slot, req);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case IO_OP_SEEK:
|
|
||||||
cmp = io_seek(slot, req);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case IO_OP_ERROR:
|
|
||||||
cmp = io_get_error(slot, req);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
cmp.error = IO_ERR_OP;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return(cmp);
|
return(cmp);
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,7 +52,7 @@ int orca_assert(const char* file, const char* function, int line, const char* sr
|
||||||
mg_font orca_font_create(const char* resourcePath)
|
mg_font orca_font_create(const char* resourcePath)
|
||||||
{
|
{
|
||||||
//NOTE(martin): create default font
|
//NOTE(martin): create default font
|
||||||
str8 fontPath = path_find_resource(mem_scratch(), STR8(resourcePath));
|
str8 fontPath = path_executable_relative(mem_scratch(), STR8(resourcePath));
|
||||||
char* fontPathCString = str8_to_cstring(mem_scratch(), fontPath);
|
char* fontPathCString = str8_to_cstring(mem_scratch(), fontPath);
|
||||||
|
|
||||||
FILE* fontFile = fopen(fontPathCString, "r");
|
FILE* fontFile = fopen(fontPathCString, "r");
|
||||||
|
@ -363,7 +363,7 @@ void* orca_runloop(void* user)
|
||||||
|
|
||||||
//NOTE: loads wasm module
|
//NOTE: loads wasm module
|
||||||
const char* bundleNameCString = "module";
|
const char* bundleNameCString = "module";
|
||||||
str8 modulePath = path_find_resource(mem_scratch(), STR8("../app/wasm/module.wasm"));
|
str8 modulePath = path_executable_relative(mem_scratch(), STR8("../app/wasm/module.wasm"));
|
||||||
const char* modulePathCString = str8_to_cstring(mem_scratch(), modulePath);
|
const char* modulePathCString = str8_to_cstring(mem_scratch(), modulePath);
|
||||||
|
|
||||||
FILE* file = fopen(modulePathCString, "rb");
|
FILE* file = fopen(modulePathCString, "rb");
|
||||||
|
@ -394,7 +394,7 @@ void* orca_runloop(void* user)
|
||||||
m3_LoadModule(app->runtime.m3Runtime, app->runtime.m3Module);
|
m3_LoadModule(app->runtime.m3Runtime, app->runtime.m3Module);
|
||||||
m3_SetModuleName(app->runtime.m3Module, bundleNameCString);
|
m3_SetModuleName(app->runtime.m3Module, bundleNameCString);
|
||||||
|
|
||||||
mem_scratch_clear();
|
mem_arena_clear(mem_scratch());
|
||||||
|
|
||||||
//NOTE: bind orca APIs
|
//NOTE: bind orca APIs
|
||||||
bindgen_link_core_api(app->runtime.m3Module);
|
bindgen_link_core_api(app->runtime.m3Module);
|
||||||
|
@ -765,7 +765,7 @@ void* orca_runloop(void* user)
|
||||||
mg_render(app->debugOverlay.surface, app->debugOverlay.canvas);
|
mg_render(app->debugOverlay.surface, app->debugOverlay.canvas);
|
||||||
mg_surface_present(app->debugOverlay.surface);
|
mg_surface_present(app->debugOverlay.surface);
|
||||||
}
|
}
|
||||||
mem_scratch_clear();
|
mem_arena_clear(mem_scratch());
|
||||||
}
|
}
|
||||||
|
|
||||||
return(0);
|
return(0);
|
||||||
|
|
Loading…
Reference in New Issue