[io] abstract file io API, using a request/completion model ala io_uring (w/ only synchronous completion for now), and per-platform backend
This commit is contained in:
parent
debffac5c0
commit
c75661cfc9
|
@ -48,8 +48,8 @@ int main()
|
||||||
mg_rect_atlas* atlas = mg_rect_atlas_create(&permanentArena, 16000, 16000);
|
mg_rect_atlas* atlas = mg_rect_atlas_create(&permanentArena, 16000, 16000);
|
||||||
mg_image atlasImage = mg_image_create(16000, 16000);
|
mg_image atlasImage = mg_image_create(16000, 16000);
|
||||||
|
|
||||||
str8 path1 = path_find_resource(mem_scratch(), STR8("../resources/triceratops.png"));
|
str8 path1 = path_executable_relative(mem_scratch(), STR8("../resources/triceratops.png"));
|
||||||
str8 path2 = path_find_resource(mem_scratch(), STR8("../resources/Top512.png"));
|
str8 path2 = path_executable_relative(mem_scratch(), STR8("../resources/Top512.png"));
|
||||||
|
|
||||||
mg_image_region image1 = mg_image_atlas_alloc_from_file(atlas, atlasImage, path1, false);
|
mg_image_region image1 = mg_image_atlas_alloc_from_file(atlas, atlasImage, path1, false);
|
||||||
mg_image_region image2 = mg_image_atlas_alloc_from_file(atlas, atlasImage, path2, false);
|
mg_image_region image2 = mg_image_atlas_alloc_from_file(atlas, atlasImage, path2, false);
|
||||||
|
|
|
@ -22,7 +22,7 @@
|
||||||
mg_font create_font()
|
mg_font create_font()
|
||||||
{
|
{
|
||||||
//NOTE(martin): create font
|
//NOTE(martin): create font
|
||||||
str8 fontPath = path_find_resource(mem_scratch(), STR8("../resources/OpenSansLatinSubset.ttf"));
|
str8 fontPath = path_executable_relative(mem_scratch(), STR8("../resources/OpenSansLatinSubset.ttf"));
|
||||||
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");
|
||||||
|
|
|
@ -38,11 +38,11 @@ int main()
|
||||||
}
|
}
|
||||||
|
|
||||||
//NOTE: create image
|
//NOTE: create image
|
||||||
str8 imagePath = path_find_resource(mem_scratch(), STR8("../resources/triceratops.png"));
|
str8 imagePath = path_executable_relative(mem_scratch(), STR8("../resources/triceratops.png"));
|
||||||
mg_image image = mg_image_create_from_file(surface, imagePath, false);
|
mg_image image = mg_image_create_from_file(surface, imagePath, false);
|
||||||
vec2 imageSize = mg_image_size(image);
|
vec2 imageSize = mg_image_size(image);
|
||||||
|
|
||||||
str8 imagePath2 = path_find_resource(mem_scratch(), STR8("../resources/Top512.png"));
|
str8 imagePath2 = path_executable_relative(mem_scratch(), STR8("../resources/Top512.png"));
|
||||||
mg_image image2 = mg_image_create_from_file(surface, imagePath2, false);
|
mg_image image2 = mg_image_create_from_file(surface, imagePath2, false);
|
||||||
vec2 imageSize2 = mg_image_size(image2);
|
vec2 imageSize2 = mg_image_size(image2);
|
||||||
|
|
||||||
|
|
|
@ -63,7 +63,7 @@ static const char* TEST_STRING =
|
||||||
mg_font create_font(const char* path)
|
mg_font create_font(const char* path)
|
||||||
{
|
{
|
||||||
//NOTE(martin): create font
|
//NOTE(martin): create font
|
||||||
str8 fontPath = path_find_resource(mem_scratch(), STR8(path));
|
str8 fontPath = path_executable_relative(mem_scratch(), STR8(path));
|
||||||
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");
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
mg_font create_font()
|
mg_font create_font()
|
||||||
{
|
{
|
||||||
//NOTE(martin): create font
|
//NOTE(martin): create font
|
||||||
str8 fontPath = path_find_resource(mem_scratch(), STR8("../resources/OpenSansLatinSubset.ttf"));
|
str8 fontPath = path_executable_relative(mem_scratch(), STR8("../resources/OpenSansLatinSubset.ttf"));
|
||||||
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");
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
mg_font create_font()
|
mg_font create_font()
|
||||||
{
|
{
|
||||||
//NOTE(martin): create font
|
//NOTE(martin): create font
|
||||||
str8 fontPath = path_find_resource(mem_scratch(), STR8("../resources/OpenSansLatinSubset.ttf"));
|
str8 fontPath = path_executable_relative(mem_scratch(), STR8("../resources/OpenSansLatinSubset.ttf"));
|
||||||
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");
|
||||||
|
|
|
@ -41,7 +41,7 @@ int main()
|
||||||
//NOTE(martin): load the library
|
//NOTE(martin): load the library
|
||||||
id<MTLDevice> device = MTLCreateSystemDefaultDevice();
|
id<MTLDevice> device = MTLCreateSystemDefaultDevice();
|
||||||
|
|
||||||
str8 shaderPath = path_find_resource(mem_scratch(), STR8("triangle_shader.metallib"));
|
str8 shaderPath = path_executable_relative(mem_scratch(), STR8("triangle_shader.metallib"));
|
||||||
const char* shaderPathCString = str8_to_cstring(mem_scratch(), shaderPath);
|
const char* shaderPathCString = str8_to_cstring(mem_scratch(), shaderPath);
|
||||||
NSString* metalFileName = [[NSString alloc] initWithCString: shaderPathCString encoding: NSUTF8StringEncoding];
|
NSString* metalFileName = [[NSString alloc] initWithCString: shaderPathCString encoding: NSUTF8StringEncoding];
|
||||||
NSError* err = 0;
|
NSError* err = 0;
|
||||||
|
|
|
@ -152,7 +152,7 @@ void debug_print_styles(ui_box* box, int indent)
|
||||||
mg_font create_font()
|
mg_font create_font()
|
||||||
{
|
{
|
||||||
//NOTE(martin): create font
|
//NOTE(martin): create font
|
||||||
str8 fontPath = path_find_resource(mem_scratch(), STR8("../resources/OpenSansLatinSubset.ttf"));
|
str8 fontPath = path_executable_relative(mem_scratch(), STR8("../resources/OpenSansLatinSubset.ttf"));
|
||||||
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");
|
||||||
|
|
|
@ -21,6 +21,7 @@
|
||||||
#elif PLATFORM_MACOS
|
#elif PLATFORM_MACOS
|
||||||
#include"platform/unix_memory.c"
|
#include"platform/unix_memory.c"
|
||||||
#include"platform/osx_clock.c"
|
#include"platform/osx_clock.c"
|
||||||
|
#include"platform/posix_io.c"
|
||||||
/*
|
/*
|
||||||
#include"platform/unix_rng.c"
|
#include"platform/unix_rng.c"
|
||||||
#include"platform/posix_thread.c"
|
#include"platform/posix_thread.c"
|
||||||
|
@ -30,6 +31,7 @@
|
||||||
#elif PLATFORM_LINUX
|
#elif PLATFORM_LINUX
|
||||||
#include"platform/unix_base_memory.c"
|
#include"platform/unix_base_memory.c"
|
||||||
#include"platform/linux_clock.c"
|
#include"platform/linux_clock.c"
|
||||||
|
#include"platform/posix_io.c"
|
||||||
/*
|
/*
|
||||||
#include"platform/unix_rng.c"
|
#include"platform/unix_rng.c"
|
||||||
#include"platform/posix_thread.c"
|
#include"platform/posix_thread.c"
|
||||||
|
|
|
@ -25,8 +25,9 @@
|
||||||
//----------------------------------------------------------------
|
//----------------------------------------------------------------
|
||||||
// platform layer
|
// platform layer
|
||||||
//----------------------------------------------------------------
|
//----------------------------------------------------------------
|
||||||
#include"platform_path.h"
|
|
||||||
#include"platform_clock.h"
|
#include"platform_clock.h"
|
||||||
|
#include"platform_path.h"
|
||||||
|
#include"platform_io.h"
|
||||||
/*
|
/*
|
||||||
#include"platform_rng.h"
|
#include"platform_rng.h"
|
||||||
#include"platform_socket.h"
|
#include"platform_socket.h"
|
||||||
|
|
|
@ -1329,7 +1329,7 @@ mg_canvas_backend* mtl_canvas_backend_create(mg_mtl_surface* surface)
|
||||||
|
|
||||||
@autoreleasepool{
|
@autoreleasepool{
|
||||||
//NOTE: load metal library
|
//NOTE: load metal library
|
||||||
str8 shaderPath = path_find_resource(mem_scratch(), STR8("mtl_renderer.metallib"));
|
str8 shaderPath = path_executable_relative(mem_scratch(), STR8("mtl_renderer.metallib"));
|
||||||
NSString* metalFileName = [[NSString alloc] initWithBytes: shaderPath.ptr length:shaderPath.len encoding: NSUTF8StringEncoding];
|
NSString* metalFileName = [[NSString alloc] initWithBytes: shaderPath.ptr length:shaderPath.len encoding: NSUTF8StringEncoding];
|
||||||
NSError* err = 0;
|
NSError* err = 0;
|
||||||
id<MTLLibrary> library = [surface->device newLibraryWithFile: metalFileName error:&err];
|
id<MTLLibrary> library = [surface->device newLibraryWithFile: metalFileName error:&err];
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
|
|
||||||
#include"platform_path.c"
|
#include"platform_path.c"
|
||||||
|
|
||||||
str8 path_find_executable(mem_arena* arena)
|
str8 path_executable(mem_arena* arena)
|
||||||
{@autoreleasepool{
|
{@autoreleasepool{
|
||||||
str8 result = {};
|
str8 result = {};
|
||||||
u32 size = 0;
|
u32 size = 0;
|
||||||
|
@ -23,32 +23,7 @@ str8 path_find_executable(mem_arena* arena)
|
||||||
return(result);
|
return(result);
|
||||||
}}
|
}}
|
||||||
|
|
||||||
str8 path_find_resource(mem_arena* arena, str8 relPath)
|
str8 path_canonical(mem_arena* arena, str8 path)
|
||||||
{
|
|
||||||
str8_list list = {};
|
|
||||||
mem_arena_scope scratch = mem_scratch_begin_next(arena);
|
|
||||||
|
|
||||||
str8 executablePath = path_find_executable(scratch.arena);
|
|
||||||
str8 dirPath = path_slice_directory(executablePath);
|
|
||||||
|
|
||||||
str8_list_push(scratch.arena, &list, dirPath);
|
|
||||||
str8_list_push(scratch.arena, &list, STR8("/"));
|
|
||||||
str8_list_push(scratch.arena, &list, relPath);
|
|
||||||
str8 path = str8_list_join(scratch.arena, list);
|
|
||||||
char* pathCString = str8_to_cstring(scratch.arena, path);
|
|
||||||
|
|
||||||
char* real = realpath(pathCString, 0);
|
|
||||||
|
|
||||||
str8 result = str8_push_cstring(arena, real);
|
|
||||||
|
|
||||||
free(real);
|
|
||||||
|
|
||||||
mem_scratch_end(scratch);
|
|
||||||
|
|
||||||
return(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
str8 path_find_canonical(mem_arena* arena, str8 path)
|
|
||||||
{
|
{
|
||||||
mem_arena_scope scratch = mem_scratch_begin_next(arena);
|
mem_arena_scope scratch = mem_scratch_begin_next(arena);
|
||||||
char* pathCString = str8_to_cstring(scratch.arena, path);
|
char* pathCString = str8_to_cstring(scratch.arena, path);
|
||||||
|
|
|
@ -0,0 +1,193 @@
|
||||||
|
/************************************************************//**
|
||||||
|
*
|
||||||
|
* @file: platform_io.h
|
||||||
|
* @author: Martin Fouilleul
|
||||||
|
* @date: 25/05/2023
|
||||||
|
*
|
||||||
|
*****************************************************************/
|
||||||
|
#ifndef __PLATFORM_IO_H_
|
||||||
|
#define __PLATFORM_IO_H_
|
||||||
|
|
||||||
|
#include"util/typedefs.h"
|
||||||
|
#include"util/strings.h"
|
||||||
|
|
||||||
|
//----------------------------------------------------------------
|
||||||
|
// IO API
|
||||||
|
//----------------------------------------------------------------
|
||||||
|
|
||||||
|
typedef struct { u64 h; } file_handle;
|
||||||
|
|
||||||
|
typedef u32 file_open_flags;
|
||||||
|
enum {
|
||||||
|
FILE_OPEN_READ = 1<<0,
|
||||||
|
FILE_OPEN_WRITE = 1<<1,
|
||||||
|
FILE_OPEN_APPEND = 1<<2,
|
||||||
|
FILE_OPEN_TRUNCATE = 1<<3,
|
||||||
|
FILE_OPEN_CREATE = 1<<4,
|
||||||
|
|
||||||
|
FILE_OPEN_NO_FOLLOW = 1<<5,
|
||||||
|
FILE_OPEN_RESTRICT = 1<<6,
|
||||||
|
//...
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef enum { FILE_SEEK_SET, FILE_SEEK_END, FILE_SEEK_CURRENT } file_whence;
|
||||||
|
|
||||||
|
typedef u64 io_req_id;
|
||||||
|
|
||||||
|
typedef u32 io_op;
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
IO_OP_OPEN_AT,
|
||||||
|
IO_OP_CLOSE,
|
||||||
|
|
||||||
|
IO_OP_FSTAT,
|
||||||
|
|
||||||
|
IO_OP_POS,
|
||||||
|
IO_OP_SEEK,
|
||||||
|
IO_OP_READ,
|
||||||
|
IO_OP_WRITE,
|
||||||
|
|
||||||
|
IO_OP_ERROR,
|
||||||
|
//...
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct io_req
|
||||||
|
{
|
||||||
|
io_req_id id;
|
||||||
|
io_op op;
|
||||||
|
file_handle handle;
|
||||||
|
|
||||||
|
i64 offset;
|
||||||
|
u64 size;
|
||||||
|
union
|
||||||
|
{
|
||||||
|
char* buffer;
|
||||||
|
u64 unused; // 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;
|
||||||
|
|
||||||
|
union
|
||||||
|
{
|
||||||
|
u64 result;
|
||||||
|
u64 size;
|
||||||
|
i64 offset;
|
||||||
|
file_handle handle;
|
||||||
|
//...
|
||||||
|
};
|
||||||
|
} io_cmp;
|
||||||
|
|
||||||
|
//----------------------------------------------------------------
|
||||||
|
//TODO: complete io queue api
|
||||||
|
//----------------------------------------------------------------
|
||||||
|
io_cmp io_wait_single_req(io_req* req);
|
||||||
|
|
||||||
|
//----------------------------------------------------------------
|
||||||
|
// File IO wrapper API
|
||||||
|
//----------------------------------------------------------------
|
||||||
|
|
||||||
|
file_handle file_open(str8 path, file_open_flags flags);
|
||||||
|
file_handle file_open_relative(file_handle base, str8 path, file_open_flags flags);
|
||||||
|
void file_close(file_handle file);
|
||||||
|
|
||||||
|
off_t file_pos(file_handle file);
|
||||||
|
off_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);
|
||||||
|
|
||||||
|
io_error io_last_error(file_handle handle);
|
||||||
|
|
||||||
|
//----------------------------------------------------------------
|
||||||
|
// File System wrapper API
|
||||||
|
//----------------------------------------------------------------
|
||||||
|
|
||||||
|
typedef enum
|
||||||
|
{
|
||||||
|
FILE_UNKNOWN,
|
||||||
|
FILE_REGULAR,
|
||||||
|
FILE_DIRECTORY,
|
||||||
|
FILE_SYMLINK,
|
||||||
|
FILE_BLOCK,
|
||||||
|
FILE_CHARACTER,
|
||||||
|
FILE_FIFO,
|
||||||
|
FILE_SOCKET,
|
||||||
|
} file_type;
|
||||||
|
|
||||||
|
typedef u16 file_perm;
|
||||||
|
enum file_perm
|
||||||
|
{
|
||||||
|
FILE_OTHER_EXEC = 1<<0,
|
||||||
|
FILE_OTHER_WRITE = 1<<1,
|
||||||
|
FILE_OTHER_READ = 1<<2,
|
||||||
|
|
||||||
|
FILE_GROUP_EXEC = 1<<3,
|
||||||
|
FILE_GROUP_WRITE = 1<<4,
|
||||||
|
FILE_GROUP_READ = 1<<5,
|
||||||
|
|
||||||
|
FILE_OWNER_EXEC = 1<<6,
|
||||||
|
FILE_OWNER_WRITE = 1<<7,
|
||||||
|
FILE_OWNER_READ = 1<<8,
|
||||||
|
|
||||||
|
FILE_STICKY_BIT = 1<<9,
|
||||||
|
FILE_SET_GID = 1<<10,
|
||||||
|
FILE_SET_UID = 1<<11,
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct file_status
|
||||||
|
{
|
||||||
|
file_type type;
|
||||||
|
file_perm perm;
|
||||||
|
u64 size;
|
||||||
|
|
||||||
|
//TODO times
|
||||||
|
|
||||||
|
} file_status;
|
||||||
|
|
||||||
|
file_status file_get_status(file_handle file);
|
||||||
|
size_t file_size(file_handle file);
|
||||||
|
|
||||||
|
//TODO: Complete as needed...
|
||||||
|
|
||||||
|
|
||||||
|
#endif //__PLATFORM_IO_H_
|
|
@ -0,0 +1,104 @@
|
||||||
|
/************************************************************//**
|
||||||
|
*
|
||||||
|
* @file: platform_io_common.c
|
||||||
|
* @author: Martin Fouilleul
|
||||||
|
* @date: 25/05/2023
|
||||||
|
*
|
||||||
|
*****************************************************************/
|
||||||
|
|
||||||
|
#include"platform_io.h"
|
||||||
|
|
||||||
|
//------------------------------------------------------------------------------
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
off_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);
|
||||||
|
}
|
||||||
|
|
||||||
|
off_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);
|
||||||
|
}
|
||||||
|
|
||||||
|
io_error io_last_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);
|
||||||
|
}
|
||||||
|
|
||||||
|
file_status file_get_status(file_handle file)
|
||||||
|
{
|
||||||
|
file_status status = {0};
|
||||||
|
io_req req = {.op = IO_OP_FSTAT,
|
||||||
|
.handle = file,
|
||||||
|
.size = sizeof(file_status),
|
||||||
|
.buffer = (char*)status};
|
||||||
|
|
||||||
|
io_cmp cmp = io_wait_single_req(&req);
|
||||||
|
return(status);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t file_size(file_handle file)
|
||||||
|
{
|
||||||
|
file_status status = file_get_status(file);
|
||||||
|
return(status.size);
|
||||||
|
}
|
|
@ -53,6 +53,7 @@ str8_list path_split(mem_arena* arena, str8 path)
|
||||||
|
|
||||||
str8 path_join(mem_arena* arena, str8_list elements)
|
str8 path_join(mem_arena* arena, str8_list elements)
|
||||||
{
|
{
|
||||||
|
//TODO: check if elements have ending/begining '/' ?
|
||||||
str8 res = str8_list_collate(arena, elements, STR8("/"), STR8("/"), (str8){0});
|
str8 res = str8_list_collate(arena, elements, STR8("/"), STR8("/"), (str8){0});
|
||||||
return(res);
|
return(res);
|
||||||
}
|
}
|
||||||
|
@ -88,3 +89,17 @@ str8 path_append(mem_arena* arena, str8 parent, str8 relPath)
|
||||||
}
|
}
|
||||||
return(result);
|
return(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
str8 path_executable_relative(mem_arena* arena, str8 relPath)
|
||||||
|
{
|
||||||
|
str8_list list = {};
|
||||||
|
mem_arena_scope scratch = mem_scratch_begin_next(arena);
|
||||||
|
|
||||||
|
str8 executablePath = path_executable(scratch.arena);
|
||||||
|
str8 dirPath = path_slice_directory(executablePath);
|
||||||
|
|
||||||
|
str8 path = path_append(arena, dirPath, relPath);
|
||||||
|
|
||||||
|
mem_scratch_end(scratch);
|
||||||
|
return(path);
|
||||||
|
}
|
||||||
|
|
|
@ -17,8 +17,10 @@ MP_API str8_list path_split(mem_arena* arena, str8 path);
|
||||||
MP_API str8 path_join(mem_arena* arena, str8_list elements);
|
MP_API str8 path_join(mem_arena* arena, str8_list elements);
|
||||||
MP_API str8 path_append(mem_arena* arena, str8 parent, str8 relPath);
|
MP_API str8 path_append(mem_arena* arena, str8 parent, str8 relPath);
|
||||||
|
|
||||||
MP_API str8 path_find_executable(mem_arena* arena);
|
MP_API str8 path_executable(mem_arena* arena);
|
||||||
MP_API str8 path_find_resource(mem_arena* arena, str8 relPath);
|
MP_API str8 path_canonical(mem_arena* arena, str8 path);
|
||||||
MP_API str8 path_find_canonical(mem_arena* arena, str8 path);
|
|
||||||
|
// helper: gets the path from path_executable() and appends relPath
|
||||||
|
MP_API str8 path_executable_relative(mem_arena* arena, str8 relPath);
|
||||||
|
|
||||||
#endif //__PLATFORM_PATH_H_
|
#endif //__PLATFORM_PATH_H_
|
||||||
|
|
|
@ -0,0 +1,498 @@
|
||||||
|
/************************************************************//**
|
||||||
|
*
|
||||||
|
* @file: posix_io.c
|
||||||
|
* @author: Martin Fouilleul
|
||||||
|
* @date: 25/05/2023
|
||||||
|
*
|
||||||
|
*****************************************************************/
|
||||||
|
|
||||||
|
#include<errno.h>
|
||||||
|
#include<fcntl.h>
|
||||||
|
#include<sys/stat.h>
|
||||||
|
#include<unistd.h>
|
||||||
|
#include<limits.h>
|
||||||
|
|
||||||
|
#include"platform_io.h"
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
int io_convert_open_flags(file_open_flags flags)
|
||||||
|
{
|
||||||
|
int oflags = 0;
|
||||||
|
|
||||||
|
if(flags & FILE_OPEN_READ)
|
||||||
|
{
|
||||||
|
if(flags & FILE_OPEN_WRITE)
|
||||||
|
{
|
||||||
|
oflags = O_RDWR;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
oflags = O_RDONLY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if(flags & FILE_OPEN_WRITE)
|
||||||
|
{
|
||||||
|
oflags = O_WRONLY;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(flags & FILE_OPEN_TRUNCATE)
|
||||||
|
{
|
||||||
|
oflags |= O_TRUNC;
|
||||||
|
}
|
||||||
|
if(flags & FILE_OPEN_APPEND)
|
||||||
|
{
|
||||||
|
oflags |= O_APPEND;
|
||||||
|
}
|
||||||
|
if(flags & FILE_OPEN_CREATE)
|
||||||
|
{
|
||||||
|
oflags |= O_CREAT;
|
||||||
|
}
|
||||||
|
if(flags & FILE_OPEN_NO_FOLLOW)
|
||||||
|
{
|
||||||
|
oflags |= O_NOFOLLOW;
|
||||||
|
}
|
||||||
|
return(oflags);
|
||||||
|
}
|
||||||
|
|
||||||
|
io_cmp io_open_at(file_slot* atSlot, 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 = io_convert_open_flags(req->openFlags);
|
||||||
|
|
||||||
|
//TODO: allow specifying access
|
||||||
|
mode_t mode = S_IRUSR
|
||||||
|
| S_IWUSR
|
||||||
|
| S_IRGRP
|
||||||
|
| S_IWGRP
|
||||||
|
| S_IROTH
|
||||||
|
| S_IWOTH;
|
||||||
|
|
||||||
|
//NOTE: open
|
||||||
|
mem_arena_scope scratch = mem_scratch_begin();
|
||||||
|
|
||||||
|
str8 path = str8_from_buffer(req->size, req->buffer);
|
||||||
|
char* pathCStr = str8_to_cstring(scratch.arena, path);
|
||||||
|
|
||||||
|
//TODO: if FILE_OPEN_RESTRICT, do the file traversal to check that path is in the
|
||||||
|
// subtree rooted at atSlot->fd
|
||||||
|
int fd = -1;
|
||||||
|
if(atSlot)
|
||||||
|
{
|
||||||
|
fd = openat(atSlot->fd, pathCStr, flags, mode);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
fd = open(pathCStr, flags, mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
mem_scratch_end(scratch);
|
||||||
|
|
||||||
|
if(fd >= 0)
|
||||||
|
{
|
||||||
|
slot->fd = fd;
|
||||||
|
}
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
file_perm io_convert_perm_from_stat(u16 mode)
|
||||||
|
{
|
||||||
|
file_perm perm = mode & 07777;
|
||||||
|
return(perm);
|
||||||
|
}
|
||||||
|
|
||||||
|
file_type io_convert_type_from_stat(u16 mode)
|
||||||
|
{
|
||||||
|
file_type type;
|
||||||
|
switch(mode & S_IFMT)
|
||||||
|
{
|
||||||
|
case S_IFIFO:
|
||||||
|
type = FILE_FIFO;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case S_IFCHR:
|
||||||
|
type = FILE_CHARACTER;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case S_IFDIR:
|
||||||
|
type = FILE_DIRECTORY;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case S_IFBLK:
|
||||||
|
type = FILE_BLOCK;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case S_IFREG:
|
||||||
|
type = FILE_REGULAR;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case S_IFLNK:
|
||||||
|
type = FILE_SYMLINK;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case S_IFSOCK:
|
||||||
|
type = FILE_SOCKET;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
type = FILE_UNKNOWN;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
io_cmp io_fstat(file_slot* slot, io_req* req)
|
||||||
|
{
|
||||||
|
io_cmp cmp = {0};
|
||||||
|
|
||||||
|
if(req->size <= sizeof(file_status))
|
||||||
|
{
|
||||||
|
cmp.error = IO_ERR_ARG;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
struct stat s;
|
||||||
|
if(fstat(slot->fd, &s))
|
||||||
|
{
|
||||||
|
slot->error = io_convert_errno(errno);
|
||||||
|
cmp.error = slot->error;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
file_status* status = (file_status*)req->buffer;
|
||||||
|
status->perm = io_convert_perm_from_stat(s.st_mode);
|
||||||
|
status->type = io_convert_type_from_stat(s.st_mode);
|
||||||
|
status->size = s.st_size;
|
||||||
|
//TODO: times
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
slot->error = io_convert_errno(errno);
|
||||||
|
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 FILE_SEEK_CURRENT:
|
||||||
|
whence = SEEK_CUR;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FILE_SEEK_SET:
|
||||||
|
whence = SEEK_SET;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FILE_SEEK_END:
|
||||||
|
whence = SEEK_END;
|
||||||
|
}
|
||||||
|
cmp.result = lseek(slot->fd, req->size, whence);
|
||||||
|
|
||||||
|
if(cmp.result < 0)
|
||||||
|
{
|
||||||
|
slot->error = io_convert_errno(errno);
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
slot->error = io_convert_errno(errno);
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
slot->error = io_convert_errno(errno);
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
io_cmp cmp = {0};
|
||||||
|
|
||||||
|
file_slot* slot = file_slot_from_handle(&__globalFileTable, req->handle);
|
||||||
|
if(!slot && (req->op != IO_OP_OPEN_AT))
|
||||||
|
{
|
||||||
|
cmp.error = IO_ERR_HANDLE;
|
||||||
|
}
|
||||||
|
else if(slot->fatal && req->op != IO_OP_CLOSE)
|
||||||
|
{
|
||||||
|
cmp.error = IO_ERR_PREV;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(cmp.error == IO_OK)
|
||||||
|
{
|
||||||
|
switch(req->op)
|
||||||
|
{
|
||||||
|
case IO_OP_OPEN_AT:
|
||||||
|
cmp = io_open_at(slot, req);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case IO_OP_FSTAT:
|
||||||
|
cmp = io_fstat(slot, req);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case IO_OP_CLOSE:
|
||||||
|
cmp = io_close(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);
|
||||||
|
}
|
Loading…
Reference in New Issue