Add new ErrorSink API to collate error from API calls

This commit is contained in:
doylet 2024-02-11 16:14:00 +11:00
parent f6c836ba84
commit 18a37024da
12 changed files with 327 additions and 116 deletions

30
dqn.h
View File

@ -24,6 +24,23 @@
// first-class APIs, a 64bit MMU and in general non-pessimized APIs that aren't
// constrained by the language specification and operate closer to the OS).
//
////////////////////////////////////////////////////////////////////////////////////////////////////
//
// $$$$$$\ $$$$$$$$\ $$$$$$$$\ $$$$$$$$\ $$$$$$\ $$\ $$\ $$$$$$\
// $$ __$$\ $$ _____|\__$$ __|\__$$ __|\_$$ _|$$$\ $$ |$$ __$$\
// $$ / \__|$$ | $$ | $$ | $$ | $$$$\ $$ |$$ / \__|
// $$ |$$$$\ $$$$$\ $$ | $$ | $$ | $$ $$\$$ |$$ |$$$$\
// $$ |\_$$ |$$ __| $$ | $$ | $$ | $$ \$$$$ |$$ |\_$$ |
// $$ | $$ |$$ | $$ | $$ | $$ | $$ |\$$$ |$$ | $$ |
// \$$$$$$ |$$$$$$$$\ $$ | $$ | $$$$$$\ $$ | \$$ |\$$$$$$ |
// \______/ \________| \__| \__| \______|\__| \__| \______/
//
// Getting started -- Compiling with the library and library documentation
//
////////////////////////////////////////////////////////////////////////////////////////////////////
//
// -- Compiling --
//
// Define DQN_IMPLEMENTATION macro in one and only one translation unit to
// enable the implementation of this library, for example:
//
@ -47,9 +64,16 @@
// Compiles the library with all optional APIs turned on except the previously
// mentioned APIs.
//
// Below is a table of contents that describes what you can find in each header
// of this library and additional macros that can be defined to customise the
// behaviour of this library.
// -- Library documentation --
//
// The header files in this library are intentionally extremely minimal and
// concise as to provide a dense reference of the APIs available without
// drowning out the library interface with code comments like many other
// documentation systems.
//
// Instead, documentation is laid out in dqn_docs.cpp in alphabetical order
// which provides self-contained examples in one contiguous top-down block of
// source code with comments.
//
////////////////////////////////////////////////////////////////////////////////////////////////////
//

View File

@ -220,6 +220,12 @@ DQN_API void Dqn_Arena_Pop(Dqn_Arena *arena, uint64_t amount)
Dqn_Arena_PopTo(arena, pop_to);
}
DQN_API uint64_t Dqn_Arena_Pos(Dqn_Arena *arena)
{
uint64_t result = (arena && arena->curr) ? arena->curr->reserve_sum + arena->curr->used : 0;
return result;
}
DQN_API void Dqn_Arena_Clear(Dqn_Arena *arena)
{
Dqn_Arena_PopTo(arena, 0);

View File

@ -145,6 +145,7 @@ DQN_API void * Dqn_Arena_AllocContiguous (Dqn_Arena *arena,
DQN_API void * Dqn_Arena_Copy (Dqn_Arena *arena, void const *data, uint64_t size, uint8_t align);
DQN_API void Dqn_Arena_PopTo (Dqn_Arena *arena, uint64_t init_used);
DQN_API void Dqn_Arena_Pop (Dqn_Arena *arena, uint64_t amount);
DQN_API uint64_t Dqn_Arena_Pos (Dqn_Arena *arena);
DQN_API void Dqn_Arena_Clear (Dqn_Arena *arena);
DQN_API Dqn_ArenaTempMem Dqn_Arena_TempMemBegin (Dqn_Arena *arena);
DQN_API void Dqn_Arena_TempMemEnd (Dqn_ArenaTempMem mem);

View File

@ -346,10 +346,10 @@ DQN_FILE_SCOPE void Dqn_Log_FVDefault_(Dqn_Str8 type, int log_type, void *user_d
// NOTE: Open log file for appending if requested //////////////////////////
Dqn_TicketMutex_Begin(&lib->log_file_mutex);
if (lib->log_to_file && !lib->log_file.handle && lib->log_file.error_size == 0) {
if (lib->log_to_file && !lib->log_file.handle && !lib->log_file.error) {
Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr);
Dqn_Str8 log_path = Dqn_OS_PathConvertF(scratch.arena, "%.*s/dqn.log", DQN_STR_FMT(lib->exe_dir));
lib->log_file = Dqn_OS_OpenFile(log_path, Dqn_OSFileOpen_CreateAlways, Dqn_OSFileAccess_AppendOnly);
lib->log_file = Dqn_OS_OpenFile(log_path, Dqn_OSFileOpen_CreateAlways, Dqn_OSFileAccess_AppendOnly, nullptr);
}
Dqn_TicketMutex_End(&lib->log_file_mutex);
@ -361,8 +361,8 @@ DQN_FILE_SCOPE void Dqn_Log_FVDefault_(Dqn_Str8 type, int log_type, void *user_d
Dqn_Print_StdLn(Dqn_PrintStd_Out, log_line);
Dqn_TicketMutex_Begin(&lib->log_file_mutex);
Dqn_OS_WriteFile(&lib->log_file, log_line);
Dqn_OS_WriteFile(&lib->log_file, DQN_STR8("\n"));
Dqn_OS_WriteFile(&lib->log_file, log_line, nullptr);
Dqn_OS_WriteFile(&lib->log_file, DQN_STR8("\n"), nullptr);
Dqn_TicketMutex_End(&lib->log_file_mutex);
}
@ -405,3 +405,97 @@ DQN_API void Dqn_Log_TypeFCallSite(Dqn_LogType type, Dqn_CallSite call_site, DQN
Dqn_Log_TypeFVCallSite(type, call_site, fmt, args);
va_end(args);
}
// NOTE: [$ERRS] Dqn_ErrorSink /////////////////////////////////////////////////////////////////////
DQN_API Dqn_ErrorSink *Dqn_ErrorSink_Begin()
{
Dqn_ThreadContext *thread_context = Dqn_ThreadContext_Get();
Dqn_ErrorSink *result = &thread_context->error_sink;
Dqn_ErrorSinkNode *node = Dqn_Arena_New(result->arena, Dqn_ErrorSinkNode, Dqn_ZeroMem_Yes);
node->next = result->stack;
node->arena_pos = Dqn_Arena_Pos(result->arena);
result->stack = node;
return result;
}
DQN_API Dqn_ErrorSinkNode Dqn_ErrorSink_End(Dqn_Arena *arena, Dqn_ErrorSink *error)
{
Dqn_ErrorSinkNode result = {};
if (!error)
return result;
Dqn_ErrorSinkNode *node = error->stack;
if (!node)
return result;
DQN_ASSERTF(arena != error->arena,
"You are not allowed to reuse the arena for ending the error sink because the memory would get popped and lost");
result = *error->stack;
result.next = nullptr;
result.msg = Dqn_Str8_Copy(arena, result.msg);
error->stack = error->stack->next;
Dqn_Arena_PopTo(error->arena, result.arena_pos);
return result;
}
DQN_API bool Dqn_ErrorSink_EndAndLogErrorFV(Dqn_ErrorSink *error, DQN_FMT_ATTRIB char const *fmt, va_list args)
{
Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr);
Dqn_ErrorSinkNode node = Dqn_ErrorSink_End(scratch.arena, error);
if (node.error) {
Dqn_Str8 line = Dqn_Str8_InitFV(scratch.arena, fmt, args);
if (Dqn_Str8_HasData(line)) {
Dqn_Log_TypeFCallSite(Dqn_LogType_Error, node.call_site, "%.*s. %.*s", DQN_STR_FMT(line), DQN_STR_FMT(node.msg));
} else {
Dqn_Log_TypeFCallSite(Dqn_LogType_Error, node.call_site, "%.*s", DQN_STR_FMT(node.msg));
}
}
bool result = node.error;
return result;
}
DQN_API bool Dqn_ErrorSink_EndAndLogErrorF(Dqn_ErrorSink *error, DQN_FMT_ATTRIB char const *fmt, ...)
{
va_list args;
va_start(args, fmt);
bool result = Dqn_ErrorSink_EndAndLogErrorFV(error, fmt, args);
va_end(args);
return result;
}
DQN_API void Dqn_ErrorSink_EndAndExitIfErrorFV(Dqn_ErrorSink *error, uint32_t exit_code, DQN_FMT_ATTRIB char const *fmt, va_list args)
{
if (Dqn_ErrorSink_EndAndLogErrorFV(error, fmt, args))
Dqn_OS_Exit(exit_code);
}
DQN_API void Dqn_ErrorSink_EndAndExitIfErrorF(Dqn_ErrorSink *error, uint32_t exit_code, DQN_FMT_ATTRIB char const *fmt, ...)
{
va_list args;
va_start(args, fmt);
Dqn_ErrorSink_EndAndExitIfErrorFV(error, exit_code, fmt, args);
va_end(args);
}
DQN_API void Dqn_ErrorSink_MakeFV_(Dqn_ErrorSink *error, uint32_t error_code, DQN_FMT_ATTRIB char const *fmt, va_list args)
{
if (error) {
Dqn_ErrorSinkNode *node = error->stack;
DQN_ASSERTF(node, "Error sink must be begun by calling 'Begin' before using this function.");
if (!node->error) { // NOTE: Only preserve the first error
node->msg = Dqn_Str8_InitFV(error->arena, fmt, args);
node->error_code = error_code;
node->error = true;
node->call_site = Dqn_ThreadContext_Get()->call_site;
}
}
}
DQN_API void Dqn_ErrorSink_MakeF_(Dqn_ErrorSink *error, uint32_t error_code, DQN_FMT_ATTRIB char const *fmt, ...)
{
va_list args;
va_start(args, fmt);
Dqn_ErrorSink_MakeFV_(error, error_code, fmt, args);
va_end(args);
}

View File

@ -385,6 +385,23 @@ struct Dqn_CallSite
};
#define DQN_CALL_SITE Dqn_CallSite{DQN_STR8(__FILE__), DQN_STR8(__func__), __LINE__}
// NOTE: [$ERRS] Dqn_ErrorSink /////////////////////////////////////////////////////////////////////
struct Dqn_ErrorSinkNode
{
bool error;
int32_t error_code;
Dqn_Str8 msg;
Dqn_CallSite call_site;
Dqn_ErrorSinkNode *next;
uint64_t arena_pos;
};
struct Dqn_ErrorSink
{
struct Dqn_Arena *arena;
Dqn_ErrorSinkNode *stack;
};
// NOTE: [$INTR] Intrinsics ////////////////////////////////////////////////////////////////////////
// NOTE: Dqn_Atomic_Add/Exchange return the previous value store in the target
#if defined(DQN_COMPILER_MSVC) || defined(DQN_COMPILER_CLANG_CL)
@ -595,6 +612,19 @@ DQN_API void Dqn_Log_TypeFCallSite
DQN_API void Dqn_Log_FVCallSite (Dqn_Str8 type, Dqn_CallSite call_site, DQN_FMT_ATTRIB char const *fmt, va_list va);
DQN_API void Dqn_Log_FCallSite (Dqn_Str8 type, Dqn_CallSite call_site, DQN_FMT_ATTRIB char const *fmt, ...);
// NOTE: [$ERRS] Dqn_ErrorSink /////////////////////////////////////////////////////////////////////
DQN_API Dqn_ErrorSink * Dqn_ErrorSink_Begin ();
DQN_API Dqn_ErrorSinkNode Dqn_ErrorSink_End (Dqn_Arena *arena, Dqn_ErrorSink *error);
DQN_API bool Dqn_ErrorSink_EndAndLogErrorFV (Dqn_ErrorSink *error, DQN_FMT_ATTRIB char const *fmt, va_list args);
DQN_API bool Dqn_ErrorSink_EndAndLogErrorF (Dqn_ErrorSink *error, DQN_FMT_ATTRIB char const *fmt, ...);
DQN_API void Dqn_ErrorSink_EndAndExitIfErrorF (Dqn_ErrorSink *error, uint32_t exit_code, DQN_FMT_ATTRIB char const *fmt, ...);
DQN_API void Dqn_ErrorSink_EndAndExitIfErrorFV (Dqn_ErrorSink *error, uint32_t exit_code, DQN_FMT_ATTRIB char const *fmt, va_list args);
#define Dqn_ErrorSink_MakeFV(error, error_code, fmt, args) do { Dqn_ThreadContext_SaveCallSite; Dqn_ErrorSink_MakeFV_(error, error_code, fmt, args); } while (0)
#define Dqn_ErrorSink_MakeF(error, error_code, fmt, ...) do { Dqn_ThreadContext_SaveCallSite; Dqn_ErrorSink_MakeF_(error, error_code, fmt, ## __VA_ARGS__); } while (0)
DQN_API void Dqn_ErrorSink_MakeFV_ (Dqn_ErrorSink *error, uint32_t error_code, DQN_FMT_ATTRIB char const *fmt, va_list args);
DQN_API void Dqn_ErrorSink_MakeF_ (Dqn_ErrorSink *error, uint32_t error_code, DQN_FMT_ATTRIB char const *fmt, ...);
// NOTE: [$INTR] Intrinsics ////////////////////////////////////////////////////////////////////////
DQN_FORCE_INLINE uint64_t Dqn_Atomic_SetValue64(uint64_t volatile *target, uint64_t value)
{

View File

@ -236,6 +236,52 @@ void Dqn_Docs_Demo()
// failure, or the requested size is smaller than the current number of
// elements in the map to resize.
// NOTE: Dqn_ErrorSink /////////////////////////////////////////////////////////////////////////
//
// A thread-local data structure that collects all errors emitted by APIs
// into one unified structure. This library has 2 core tenets when handling
// errors
//
// 1. Pipelining of errors
// Errors emitted over the course of several API calls are accumulated
// into a thread-local sink which save the error code and message
// of the first error encountered.
//
// 2. Error proof APIs
// Functions that produce errors must return objects/handles that are
// marked to trigger no-ops used in subsequent functions dependent on it.
//
// Together this allows end-users of APIs to chain calls and defer error
// checking until the end of a sequence of actions. Consider the following
// example demonstrating the 2 approaches.
// (A) Conventional error checking patterns using return/sentinel values
#if 0
FileHandle *file = OpenFile("/path/to/file");
if (!file)
// Error handling!
if (!WriteFile(file, "abc"))
// Error handling!
CloseFile(file);
#endif
// (B) Error handling using pipelining and and error proof APIs
if (0) {
Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr);
Dqn_ErrorSink *error = Dqn_ErrorSink_Begin();
Dqn_OSFile file = Dqn_OS_OpenFile(DQN_STR8("/path/to/file"), Dqn_OSFileOpen_OpenIfExist, Dqn_OSFileAccess_ReadWrite, error);
Dqn_OS_WriteFile(&file, DQN_STR8("abc"), error);
Dqn_OS_CloseFile(&file);
Dqn_ErrorSinkNode error_node = Dqn_ErrorSink_End(scratch.arena, error);
if (error_node.error) {
// Do error handling!
Dqn_Log_ErrorF("%.*s", DQN_STR_FMT(error_node.msg));
}
}
// TODO(doyle): Integrate more into the codebase and provide a concrete example.
// NOTE: Dqn_FStr8_Max /////////////////////////////////////////////////////////////////////////
//
// Return the maximum capacity of the string, e.g. the 'N' template
@ -370,7 +416,10 @@ void Dqn_Docs_Demo()
// If 'tmp_path' is written to successfuly, the file will be copied over into
// 'path'.
if (0) {
Dqn_OS_WriteAllSafe(/*path*/ DQN_STR8("C:/Home/my.txt"), /*buffer*/ DQN_STR8("Hello world"));
Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr);
Dqn_ErrorSink *error = Dqn_ErrorSink_Begin();
Dqn_OS_WriteAllSafe(/*path*/ DQN_STR8("C:/Home/my.txt"), /*buffer*/ DQN_STR8("Hello world"), error);
Dqn_ErrorSink_EndAndLogErrorF(error, "");
}
// NOTE: Dqn_OS_EstimateTSCPerSecond ///////////////////////////////////////////////////////////

View File

@ -148,95 +148,84 @@ DQN_API uint64_t Dqn_OS_EstimateTSCPerSecond(uint64_t duration_ms_to_gauge_tsc_f
#if !defined(DQN_NO_OS_FILE_API)
// NOTE: [$FILE] Dqn_OSPathInfo/File ///////////////////////////////////////////////////////////////
DQN_API bool Dqn_OS_WriteFile(Dqn_OSFile *file, Dqn_Str8 buffer)
DQN_API bool Dqn_OS_WriteFile(Dqn_OSFile *file, Dqn_Str8 buffer, Dqn_ErrorSink *error)
{
bool result = Dqn_OS_WriteFileBuffer(file, buffer.data, buffer.size);
bool result = Dqn_OS_WriteFileBuffer(file, buffer.data, buffer.size, error);
return result;
}
DQN_API bool Dqn_OS_WriteFileFV(Dqn_OSFile *file, DQN_FMT_ATTRIB char const *fmt, va_list args)
DQN_API bool Dqn_OS_WriteFileFV(Dqn_OSFile *file, Dqn_ErrorSink *error, DQN_FMT_ATTRIB char const *fmt, va_list args)
{
bool result = false;
if (!file || !fmt)
return result;
Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr);
Dqn_Str8 buffer = Dqn_Str8_InitFV(scratch.arena, fmt, args);
result = Dqn_OS_WriteFileBuffer(file, buffer.data, buffer.size);
result = Dqn_OS_WriteFileBuffer(file, buffer.data, buffer.size, error);
return result;
}
DQN_API bool Dqn_OS_WriteFileF(Dqn_OSFile *file, DQN_FMT_ATTRIB char const *fmt, ...)
DQN_API bool Dqn_OS_WriteFileF(Dqn_OSFile *file, Dqn_ErrorSink *error, DQN_FMT_ATTRIB char const *fmt, ...)
{
va_list args;
va_start(args, fmt);
bool result = Dqn_OS_WriteFileFV(file, fmt, args);
bool result = Dqn_OS_WriteFileFV(file, error, fmt, args);
va_end(args);
return result;
}
// NOTE: R/W Entire File ///////////////////////////////////////////////////////////////////////////
DQN_API bool Dqn_OS_WriteAll(Dqn_Str8 path, Dqn_Str8 buffer)
DQN_API bool Dqn_OS_WriteAll(Dqn_Str8 path, Dqn_Str8 buffer, Dqn_ErrorSink *error)
{
Dqn_OSFile file = Dqn_OS_OpenFile(path, Dqn_OSFileOpen_CreateAlways, Dqn_OSFileAccess_Write);
bool result = Dqn_OS_WriteFile(&file, buffer);
Dqn_OSFile file = Dqn_OS_OpenFile(path, Dqn_OSFileOpen_CreateAlways, Dqn_OSFileAccess_Write, error);
bool result = Dqn_OS_WriteFile(&file, buffer, error);
Dqn_OS_CloseFile(&file);
return result;
}
DQN_API bool Dqn_OS_WriteAllFV(Dqn_Str8 file_path, DQN_FMT_ATTRIB char const *fmt, va_list args)
DQN_API bool Dqn_OS_WriteAllFV(Dqn_Str8 file_path, Dqn_ErrorSink *error, DQN_FMT_ATTRIB char const *fmt, va_list args)
{
Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr);
Dqn_Str8 buffer = Dqn_Str8_InitFV(scratch.arena, fmt, args);
bool result = Dqn_OS_WriteAll(file_path, buffer);
Dqn_Str8 buffer = Dqn_Str8_InitFV(scratch.arena, fmt, args);
bool result = Dqn_OS_WriteAll(file_path, buffer, error);
return result;
}
DQN_API bool Dqn_OS_WriteAllF(Dqn_Str8 file_path, DQN_FMT_ATTRIB char const *fmt, ...)
DQN_API bool Dqn_OS_WriteAllF(Dqn_Str8 file_path, Dqn_ErrorSink *error, DQN_FMT_ATTRIB char const *fmt, ...)
{
va_list args;
va_start(args, fmt);
bool result = Dqn_OS_WriteAllFV(file_path, fmt, args);
bool result = Dqn_OS_WriteAllFV(file_path, error, fmt, args);
va_end(args);
return result;
}
DQN_API bool Dqn_OS_WriteAllSafe(Dqn_Str8 path, Dqn_Str8 buffer)
DQN_API bool Dqn_OS_WriteAllSafe(Dqn_Str8 path, Dqn_Str8 buffer, Dqn_ErrorSink *error)
{
Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr);
Dqn_Str8 tmp_path = Dqn_Str8_InitF(scratch.arena, "%.*s.tmp", DQN_STR_FMT(path));
if (!Dqn_OS_WriteAll(tmp_path, buffer)) {
Dqn_Log_ErrorF("Failed to write to temporary file [path=%.*s]", DQN_STR_FMT(tmp_path));
if (!Dqn_OS_WriteAll(tmp_path, buffer, error))
return false;
}
if (!Dqn_OS_FileCopy(tmp_path, path, true /*overwrite*/)) {
Dqn_Log_ErrorF("Failed to overwrite file at '%.*s' with temporary file '%.*s' to complete the safe-write",
DQN_STR_FMT(tmp_path),
DQN_STR_FMT(path));
if (!Dqn_OS_FileCopy(tmp_path, path, true /*overwrite*/, error))
return false;
}
if (!Dqn_OS_PathDelete(tmp_path)) {
Dqn_Log_ErrorF("Failed to delete the temporary file at '%.*s' to clean-up the safe-write", DQN_STR_FMT(tmp_path));
if (!Dqn_OS_PathDelete(tmp_path))
return false;
}
return true;
}
DQN_API bool Dqn_OS_WriteAllSafeFV(Dqn_Str8 path, DQN_FMT_ATTRIB char const *fmt, va_list args)
DQN_API bool Dqn_OS_WriteAllSafeFV(Dqn_Str8 path, Dqn_ErrorSink *error, DQN_FMT_ATTRIB char const *fmt, va_list args)
{
Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr);
Dqn_Str8 buffer = Dqn_Str8_InitFV(scratch.arena, fmt, args);
bool result = Dqn_OS_WriteAllSafe(path, buffer);
bool result = Dqn_OS_WriteAllSafe(path, buffer, error);
return result;
}
DQN_API bool Dqn_OS_WriteAllSafeF(Dqn_Str8 path, DQN_FMT_ATTRIB char const *fmt, ...)
DQN_API bool Dqn_OS_WriteAllSafeF(Dqn_Str8 path, Dqn_ErrorSink *error, DQN_FMT_ATTRIB char const *fmt, ...)
{
va_list args;
va_start(args, fmt);
bool result = Dqn_OS_WriteAllSafeFV(path, fmt, args);
bool result = Dqn_OS_WriteAllSafeFV(path, error, fmt, args);
return result;
}
#endif // !defined(DQN_NO_OS_FILE_API)

View File

@ -120,9 +120,8 @@ struct Dqn_OSPathInfo
// NOTE: R/W Stream API ////////////////////////////////////////////////////////////////////////////
struct Dqn_OSFile
{
void *handle;
char error[512];
uint16_t error_size;
bool error;
void *handle;
};
enum Dqn_OSFileOpen
@ -305,27 +304,27 @@ DQN_API uint64_t Dqn_OS_EstimateTSCPerSecond(uint64_t duration_
DQN_API Dqn_OSPathInfo Dqn_OS_PathInfo (Dqn_Str8 path);
DQN_API bool Dqn_OS_PathDelete(Dqn_Str8 path);
DQN_API bool Dqn_OS_FileExists(Dqn_Str8 path);
DQN_API bool Dqn_OS_FileCopy (Dqn_Str8 src, Dqn_Str8 dest, bool overwrite);
DQN_API bool Dqn_OS_FileMove (Dqn_Str8 src, Dqn_Str8 dest, bool overwrite);
DQN_API bool Dqn_OS_DirExists (Dqn_Str8 path);
DQN_API bool Dqn_OS_DirMake (Dqn_Str8 path);
DQN_API bool Dqn_OS_FileCopy (Dqn_Str8 src, Dqn_Str8 dest, bool overwrite, Dqn_ErrorSink *error);
DQN_API bool Dqn_OS_FileMove (Dqn_Str8 src, Dqn_Str8 dest, bool overwrite, Dqn_ErrorSink *error);
DQN_API bool Dqn_OS_DirExists (Dqn_Str8 path, Dqn_ErrorSink *error);
DQN_API bool Dqn_OS_DirMake (Dqn_Str8 path, Dqn_ErrorSink *error);
// NOTE: R/W Stream API ////////////////////////////////////////////////////////////////////////////
DQN_API Dqn_OSFile Dqn_OS_OpenFile (Dqn_Str8 path, Dqn_OSFileOpen open_mode, uint32_t access);
DQN_API bool Dqn_OS_WriteFileBuffer(Dqn_OSFile *file, void const *data, Dqn_usize size);
DQN_API bool Dqn_OS_WriteFile (Dqn_OSFile *file, Dqn_Str8 buffer);
DQN_API bool Dqn_OS_WriteFileFV (Dqn_OSFile *file, DQN_FMT_ATTRIB char const *fmt, va_list args);
DQN_API bool Dqn_OS_WriteFileF (Dqn_OSFile *file, DQN_FMT_ATTRIB char const *fmt, ...);
DQN_API Dqn_OSFile Dqn_OS_OpenFile (Dqn_Str8 path, Dqn_OSFileOpen open_mode, uint32_t access, Dqn_ErrorSink *error);
DQN_API bool Dqn_OS_WriteFileBuffer(Dqn_OSFile *file, void const *data, Dqn_usize size, Dqn_ErrorSink *error);
DQN_API bool Dqn_OS_WriteFile (Dqn_OSFile *file, Dqn_Str8 buffer, Dqn_ErrorSink *error);
DQN_API bool Dqn_OS_WriteFileFV (Dqn_OSFile *file, Dqn_ErrorSink *error, DQN_FMT_ATTRIB char const *fmt, va_list args);
DQN_API bool Dqn_OS_WriteFileF (Dqn_OSFile *file, Dqn_ErrorSink *error, DQN_FMT_ATTRIB char const *fmt, ...);
DQN_API void Dqn_OS_CloseFile (Dqn_OSFile *file);
// NOTE: R/W Entire File ///////////////////////////////////////////////////////////////////////////
DQN_API Dqn_Str8 Dqn_OS_ReadAll (Dqn_Str8 path, Dqn_Arena *arena);
DQN_API bool Dqn_OS_WriteAll (Dqn_Str8 path, Dqn_Str8 buffer);
DQN_API bool Dqn_OS_WriteAllFV (Dqn_Str8 path, DQN_FMT_ATTRIB char const *fmt, va_list args);
DQN_API bool Dqn_OS_WriteAllF (Dqn_Str8 path, DQN_FMT_ATTRIB char const *fmt, ...);
DQN_API bool Dqn_OS_WriteAllSafe (Dqn_Str8 path, Dqn_Str8 buffer);
DQN_API bool Dqn_OS_WriteAllSafeFV(Dqn_Str8 path, DQN_FMT_ATTRIB char const *fmt, va_list args);
DQN_API bool Dqn_OS_WriteAllSafeF (Dqn_Str8 path, DQN_FMT_ATTRIB char const *fmt, ...);
DQN_API Dqn_Str8 Dqn_OS_ReadAll (Dqn_Str8 path, Dqn_Arena *arena, Dqn_ErrorSink *error);
DQN_API bool Dqn_OS_WriteAll (Dqn_Str8 path, Dqn_Str8 buffer, Dqn_ErrorSink *error);
DQN_API bool Dqn_OS_WriteAllFV (Dqn_Str8 path, Dqn_ErrorSink *error, DQN_FMT_ATTRIB char const *fmt, va_list args);
DQN_API bool Dqn_OS_WriteAllF (Dqn_Str8 path, Dqn_ErrorSink *error, DQN_FMT_ATTRIB char const *fmt, ...);
DQN_API bool Dqn_OS_WriteAllSafe (Dqn_Str8 path, Dqn_Str8 buffer, Dqn_ErrorSink *error);
DQN_API bool Dqn_OS_WriteAllSafeFV(Dqn_Str8 path, Dqn_ErrorSink *error, DQN_FMT_ATTRIB char const *fmt, va_list args);
DQN_API bool Dqn_OS_WriteAllSafeF (Dqn_Str8 path, Dqn_ErrorSink *error, DQN_FMT_ATTRIB char const *fmt, ...);
#endif // !defined(DQN_NO_OS_FILE_API)
// NOTE: File system paths /////////////////////////////////////////////////////////////////////////

View File

@ -254,7 +254,7 @@ DQN_API bool Dqn_OS_FileExists(Dqn_Str8 path)
return result;
}
DQN_API bool Dqn_OS_FileCopy(Dqn_Str8 src, Dqn_Str8 dest, bool overwrite)
DQN_API bool Dqn_OS_FileCopy(Dqn_Str8 src, Dqn_Str8 dest, bool overwrite, Dqn_ErrorSink *error)
{
bool result = false;
Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr);
@ -262,19 +262,22 @@ DQN_API bool Dqn_OS_FileCopy(Dqn_Str8 src, Dqn_Str8 dest, bool overwrite)
Dqn_Str16 dest16 = Dqn_Win_Str8ToStr16(scratch.arena, dest);
int fail_if_exists = overwrite == false;
result = CopyFileW(src16.data, dest16.data, fail_if_exists) != 0;
result = CopyFileW(src16.data, dest16.data, fail_if_exists) != 0;
if (!result) {
Dqn_WinError error = Dqn_Win_LastError(scratch.arena);
Dqn_Log_ErrorF("Failed to copy the file\n\nSource: %.*s\nDestination: %.*s\n\nWindows reported: %.*s",
DQN_STR_FMT(src),
DQN_STR_FMT(dest),
DQN_STR_FMT(error.msg));
Dqn_WinError win_error = Dqn_Win_LastError(scratch.arena);
Dqn_ErrorSink_MakeF(error,
win_error.code,
"Failed to copy file '%.*s' to '%.*s': (%u) %.*s",
DQN_STR_FMT(src),
DQN_STR_FMT(dest),
win_error.code,
DQN_STR_FMT(win_error.msg));
}
return result;
}
DQN_API bool Dqn_OS_FileMove(Dqn_Str8 src, Dqn_Str8 dest, bool overwrite)
DQN_API bool Dqn_OS_FileMove(Dqn_Str8 src, Dqn_Str8 dest, bool overwrite, Dqn_ErrorSink *error)
{
bool result = false;
Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr);
@ -288,11 +291,14 @@ DQN_API bool Dqn_OS_FileMove(Dqn_Str8 src, Dqn_Str8 dest, bool overwrite)
result = MoveFileExW(src16.data, dest16.data, flags) != 0;
if (!result) {
Dqn_WinError error = Dqn_Win_LastError(scratch.arena);
Dqn_Log_ErrorF("Failed to move the file\n\nSource: %.*s\nDestination: %.*s\n\nWindows reported: %.*s",
DQN_STR_FMT(src),
DQN_STR_FMT(dest),
DQN_STR_FMT(error.msg));
Dqn_WinError win_error = Dqn_Win_LastError(scratch.arena);
Dqn_ErrorSink_MakeF(error,
win_error.code,
"Failed to move file '%.*s' to '%.*s': (%u) %.*s",
DQN_STR_FMT(src),
DQN_STR_FMT(dest),
win_error.code,
DQN_STR_FMT(win_error.msg));
}
return result;
}
@ -385,7 +391,7 @@ DQN_API bool Dqn_OS_PathDelete(Dqn_Str8 path)
}
// NOTE: R/W Stream API ////////////////////////////////////////////////////////////////////////////
DQN_API Dqn_OSFile Dqn_OS_OpenFile(Dqn_Str8 path, Dqn_OSFileOpen open_mode, uint32_t access)
DQN_API Dqn_OSFile Dqn_OS_OpenFile(Dqn_Str8 path, Dqn_OSFileOpen open_mode, uint32_t access, Dqn_ErrorSink *error)
{
Dqn_OSFile result = {};
if (!Dqn_Str8_HasData(path) || path.size <= 0)
@ -429,13 +435,9 @@ DQN_API Dqn_OSFile Dqn_OS_OpenFile(Dqn_Str8 path, Dqn_OSFileOpen open_mode, uint
/*HANDLE hTemplateFile*/ nullptr);
if (handle == INVALID_HANDLE_VALUE) {
Dqn_WinError error = Dqn_Win_LastError(scratch.arena);
result.error_size =
DQN_CAST(uint16_t) Dqn_FmtBuffer3DotTruncate(result.error,
DQN_ARRAY_UCOUNT(result.error),
"Open file failed: %.*s for \"%.*s\"",
DQN_STR_FMT(error.msg),
DQN_STR_FMT(path));
Dqn_WinError win_error = Dqn_Win_LastError(scratch.arena);
result.error = true;
Dqn_ErrorSink_MakeF(error, win_error.code, "Failed to open file at '%.*s': '%.*s'", DQN_STR_FMT(path), DQN_STR_FMT(win_error.msg));
return result;
}
@ -443,9 +445,9 @@ DQN_API Dqn_OSFile Dqn_OS_OpenFile(Dqn_Str8 path, Dqn_OSFileOpen open_mode, uint
return result;
}
DQN_API bool Dqn_OS_WriteFileBuffer(Dqn_OSFile *file, void const *buffer, Dqn_usize size)
DQN_API bool Dqn_OS_WriteFileBuffer(Dqn_OSFile *file, void const *buffer, Dqn_usize size, Dqn_ErrorSink *error)
{
if (!file || !file->handle || !buffer || size <= 0 || file->error_size)
if (!file || !file->handle || file->error || !buffer || size <= 0)
return false;
bool result = true;
@ -458,28 +460,24 @@ DQN_API bool Dqn_OS_WriteFileBuffer(Dqn_OSFile *file, void const *buffer, Dqn_us
}
if (!result) {
Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr);
Dqn_WinError error = Dqn_Win_LastError(scratch.arena);
file->error_size =
DQN_CAST(uint16_t) Dqn_FmtBuffer3DotTruncate(file->error,
DQN_ARRAY_UCOUNT(file->error),
"Write file failed (%u): %.*s",
error.code,
DQN_STR_FMT(error.msg));
Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr);
Dqn_WinError win_error = Dqn_Win_LastError(scratch.arena);
Dqn_Str8 buffer_size_str8 = Dqn_U64ToByteSizeStr8(scratch.arena, size, Dqn_U64ByteSizeType_Auto);
Dqn_ErrorSink_MakeF(error, win_error.code, "Failed to write buffer (%.*s) to file handle: %.*s", DQN_STR_FMT(buffer_size_str8), DQN_STR_FMT(win_error.msg));
}
return result;
}
DQN_API void Dqn_OS_CloseFile(Dqn_OSFile *file)
{
if (!file || !file->handle || file->error_size)
if (!file || !file->handle || file->error)
return;
CloseHandle(file->handle);
*file = {};
}
// NOTE: R/W Entire File ///////////////////////////////////////////////////////////////////////////
DQN_API Dqn_Str8 Dqn_OS_ReadAll(Dqn_Str8 path, Dqn_Arena *arena)
DQN_API Dqn_Str8 Dqn_OS_ReadAll(Dqn_Str8 path, Dqn_Arena *arena, Dqn_ErrorSink *error)
{
Dqn_Str8 result = {};
if (!arena)
@ -499,8 +497,8 @@ DQN_API Dqn_Str8 Dqn_OS_ReadAll(Dqn_Str8 path, Dqn_Arena *arena)
/*DWORD dwFlagsAndAttributes*/ FILE_ATTRIBUTE_READONLY,
/*HANDLE hTemplateFile*/ nullptr);
if (file_handle == INVALID_HANDLE_VALUE) {
Dqn_WinError error = Dqn_Win_LastError(scratch.arena);
Dqn_Log_ErrorF("Failed to open file '%.*s' for reading: (%u) %.*s", DQN_STR_FMT(path), error.code, DQN_STR_FMT(error.msg));
Dqn_WinError win_error = Dqn_Win_LastError(scratch.arena);
Dqn_ErrorSink_MakeF(error, win_error.code, "Failed to open file at '%.*s' for reading: %.*s", DQN_STR_FMT(path), DQN_STR_FMT(win_error.msg));
return result;
}
DQN_DEFER { CloseHandle(file_handle); };
@ -508,14 +506,14 @@ DQN_API Dqn_Str8 Dqn_OS_ReadAll(Dqn_Str8 path, Dqn_Arena *arena)
// NOTE: Query the file size ///////////////////////////////////////////////////////////////////
LARGE_INTEGER win_file_size;
if (!GetFileSizeEx(file_handle, &win_file_size)) {
Dqn_WinError error = Dqn_Win_LastError(scratch.arena);
Dqn_Log_ErrorF("Failed to query file size [file=%.*s, reason=%.*s]", DQN_STR_FMT(path), DQN_STR_FMT(error.msg));
Dqn_WinError win_error = Dqn_Win_LastError(scratch.arena);
Dqn_ErrorSink_MakeF(error, win_error.code, "Failed to query file size for reading '%.*s': %.*s", DQN_STR_FMT(path), DQN_STR_FMT(win_error.msg));
return result;
}
unsigned long const bytes_desired = DQN_CAST(unsigned long)win_file_size.QuadPart;
if (!DQN_CHECKF(bytes_desired == win_file_size.QuadPart,
"Current implementation doesn't support >4GiB, implement Win32 overlapped IO")) {
if (!DQN_CHECK(bytes_desired == win_file_size.QuadPart)) {
Dqn_ErrorSink_MakeF(error, 1 /*error_code*/, "Current implementation doesn't support reading >4GiB file at '%.*s', implement Win32 overlapped IO", DQN_STR_FMT(path));
return result;
}
@ -529,19 +527,22 @@ DQN_API Dqn_Str8 Dqn_OS_ReadAll(Dqn_Str8 path, Dqn_Arena *arena)
/*LPOVERLAPPED lpOverlapped*/ nullptr);
if (read_result == 0) {
Dqn_WinError error = Dqn_Win_LastError(scratch.arena);
Dqn_Log_ErrorF("Failed to load file '%.*s' (via 'ReadFile'): (%u) %.*s", DQN_STR_FMT(path), error.code, DQN_STR_FMT(error.msg));
Dqn_WinError win_error = Dqn_Win_LastError(scratch.arena);
Dqn_ErrorSink_MakeF(error, win_error.code, "Failed to read bytes from file '%.*s': (%u) %.*s", DQN_STR_FMT(path), win_error.code, DQN_STR_FMT(win_error.msg));
return result;
}
if (bytes_read != bytes_desired) {
Dqn_WinError error = Dqn_Win_LastError(scratch.arena);
Dqn_Log_ErrorF("Loaded file '%.*s' (via 'ReadFile') did not read the desired number of bytes %u, we read %u: (%u) %.*s",
DQN_STR_FMT(path),
bytes_desired,
bytes_read,
error.code,
DQN_STR_FMT(error.msg));
Dqn_WinError win_error = Dqn_Win_LastError(scratch.arena);
Dqn_ErrorSink_MakeF(
error,
win_error.code,
"Failed to read the desired number of bytes from file '%.*s', we read %uB but we expected %uB: (%u) %.*s",
DQN_STR_FMT(path),
bytes_read,
bytes_desired,
win_error.code,
DQN_STR_FMT(win_error.msg));
return result;
}

View File

@ -55,7 +55,7 @@ DQN_API Dqn_ThreadContext *Dqn_ThreadContext_Get()
Dqn_ArenaCatalog *catalog = &g_dqn_library->arena_catalog;
DQN_HARD_ASSERTF(g_dqn_library && g_dqn_library->lib_init, "Library must be initialised by calling Dqn_Library_Init()");
// NOTE: Setup scratch arenas
// NOTE: Setup scratch arenas //////////////////////////////////////////////////////////////////
DQN_FOR_UINDEX (index, DQN_ARRAY_UCOUNT(result->scratch_arenas)) {
// NOTE: We allocate arenas so that they all come from the memory
@ -85,6 +85,20 @@ DQN_API Dqn_ThreadContext *Dqn_ThreadContext_Get()
result->scratch_arenas[index] = catalog_item->arena;
}
}
// NOTE: Setup error sink //////////////////////////////////////////////////////////////////////
{
Dqn_FStr8<128> label = Dqn_FStr8_InitF<128>("T%05u Error Sink", Dqn_OS_ThreadID());
Dqn_ArenaCatalogItem *catalog_item = Dqn_ArenaCatalog_Find(catalog, Dqn_FStr8_ToStr8(&label));
if (catalog_item == &catalog->sentinel) {
result->error_sink_arena = Dqn_ArenaCatalog_AllocLabelCopy(catalog, 0, 0, Dqn_ArenaFlag_AllocCanLeak, Dqn_FStr8_ToStr8(&label));
} else {
// NOTE: Reuse the arena
result->error_sink_arena = catalog_item->arena;
}
result->error_sink.arena = result->error_sink_arena;
}
return result;
}

View File

@ -24,8 +24,11 @@
struct Dqn_ThreadContext
{
Dqn_b32 init;
Dqn_Arena *scratch_arenas[2];
Dqn_b32 init;
Dqn_Arena *scratch_arenas[2];
Dqn_Arena *error_sink_arena;
Dqn_CallSite call_site;
Dqn_ErrorSink error_sink;
};
struct Dqn_Scratch
@ -40,4 +43,5 @@ struct Dqn_Scratch
DQN_API bool Dqn_ThreadContext_IsInit();
DQN_API Dqn_ThreadContext *Dqn_ThreadContext_Get();
#define Dqn_ThreadContext_SaveCallSite do { Dqn_ThreadContext_Get()->call_site = DQN_CALL_SITE; } while (0)
DQN_API Dqn_Scratch Dqn_Scratch_Get(void const *conflict_arena);

View File

@ -710,28 +710,28 @@ static Dqn_UTest Dqn_Test_Fs()
DQN_UTEST_TEST("File write, read, copy, move and delete") {
// NOTE: Write step
Dqn_Str8 const SRC_FILE = DQN_STR8("dqn_test_file");
Dqn_b32 write_result = Dqn_OS_WriteAll(SRC_FILE, DQN_STR8("test"));
Dqn_b32 write_result = Dqn_OS_WriteAll(SRC_FILE, DQN_STR8("test"), nullptr);
DQN_UTEST_ASSERT(&test, write_result);
DQN_UTEST_ASSERT(&test, Dqn_OS_FileExists(SRC_FILE));
// NOTE: Read step
Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr);
Dqn_Str8 read_file = Dqn_OS_ReadAll(SRC_FILE, scratch.arena);
Dqn_Str8 read_file = Dqn_OS_ReadAll(SRC_FILE, scratch.arena, nullptr);
DQN_UTEST_ASSERTF(&test, Dqn_Str8_HasData(read_file), "Failed to load file");
DQN_UTEST_ASSERTF(&test, read_file.size == 4, "File read wrong amount of bytes");
DQN_UTEST_ASSERTF(&test, Dqn_Str8_Eq(read_file, DQN_STR8("test")), "read(%zu): %.*s", read_file.size, DQN_STR_FMT(read_file));
// NOTE: Copy step
Dqn_Str8 const COPY_FILE = DQN_STR8("dqn_test_file_copy");
Dqn_b32 copy_result = Dqn_OS_FileCopy(SRC_FILE, COPY_FILE, true /*overwrite*/);
Dqn_b32 copy_result = Dqn_OS_FileCopy(SRC_FILE, COPY_FILE, true /*overwrite*/, nullptr);
DQN_UTEST_ASSERT(&test, copy_result);
DQN_UTEST_ASSERT(&test, Dqn_OS_FileExists(COPY_FILE));
// NOTE: Move step
Dqn_Str8 const MOVE_FILE = DQN_STR8("dqn_test_file_move");
Dqn_b32 move_result = Dqn_OS_FileMove(COPY_FILE, MOVE_FILE, true /*overwrite*/);
Dqn_b32 move_result = Dqn_OS_FileMove(COPY_FILE, MOVE_FILE, true /*overwrite*/, nullptr);
DQN_UTEST_ASSERT(&test, move_result);
DQN_UTEST_ASSERT(&test, Dqn_OS_FileExists(MOVE_FILE));
DQN_UTEST_ASSERT(&test, Dqn_OS_FileExists(MOVE_FILE));
DQN_UTEST_ASSERTF(&test, Dqn_OS_FileExists(COPY_FILE) == false, "Moving a file should remove the original");
// NOTE: Delete step