From 8b2aea5b1d6d9c38231656641b3f75c0bc496bb5 Mon Sep 17 00:00:00 2001 From: doylet Date: Sun, 11 Feb 2024 18:23:13 +1100 Subject: [PATCH] Implement error sink on posix layer --- dqn_base.cpp | 6 +- dqn_containers.h | 2 +- dqn_cppbuild.h | 4 +- dqn_docs.cpp | 82 ++++++++++++------ dqn_os.cpp | 56 +++++++++--- dqn_os.h | 19 +++-- dqn_os_posix.cpp | 207 +++++++++++++++++++++++++-------------------- dqn_os_win32.cpp | 180 ++++++++++++++++----------------------- dqn_unit_tests.cpp | 6 +- 9 files changed, 312 insertions(+), 250 deletions(-) diff --git a/dqn_base.cpp b/dqn_base.cpp index 2f7ac55..486c287 100644 --- a/dqn_base.cpp +++ b/dqn_base.cpp @@ -349,7 +349,7 @@ DQN_FILE_SCOPE void Dqn_Log_FVDefault_(Dqn_Str8 type, int log_type, void *user_d 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, nullptr); + lib->log_file = Dqn_OS_FileOpen(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, nullptr); - Dqn_OS_WriteFile(&lib->log_file, DQN_STR8("\n"), nullptr); + Dqn_OS_FileWrite(&lib->log_file, log_line, nullptr); + Dqn_OS_FileWrite(&lib->log_file, DQN_STR8("\n"), nullptr); Dqn_TicketMutex_End(&lib->log_file_mutex); } diff --git a/dqn_containers.h b/dqn_containers.h index 3e0eb1f..7745c59 100644 --- a/dqn_containers.h +++ b/dqn_containers.h @@ -478,7 +478,7 @@ template Dqn_VArray Dqn_VArray_Init(Dqn_usize max, uint8_t arena template Dqn_VArray Dqn_VArray_InitSlice(Dqn_Slice slice, Dqn_usize max, uint8_t arena_flags) { Dqn_usize real_max = DQN_MAX(slice.size, max); - Dqn_VArray result = Dqn_VArray_Init(real_max, arena_flags); + Dqn_VArray result = Dqn_VArray_Init(real_max, arena_flags); if (Dqn_VArray_IsValid(&result)) Dqn_VArray_AddArray(&result, slice.data, slice.size); return result; diff --git a/dqn_cppbuild.h b/dqn_cppbuild.h index 430ccd7..3e8b4ca 100644 --- a/dqn_cppbuild.h +++ b/dqn_cppbuild.h @@ -179,7 +179,7 @@ DQN_API Dqn_CPPBuildAsyncResult Dqn_CPPBuild_Async(Dqn_CPPBuildContext build_con if (!cmd_line.size) return result; - if (!Dqn_OS_DirMake(build_context.build_dir)) { + if (!Dqn_OS_MakeDir(build_context.build_dir)) { result.status = Dqn_CPPBuildStatus_BuildDirectoryFailedToBeMade; return result; } @@ -190,7 +190,7 @@ DQN_API Dqn_CPPBuildAsyncResult Dqn_CPPBuild_Async(Dqn_CPPBuildContext build_con void Dqn_CPPBuild_ExecOrAbort(Dqn_CPPBuildContext build_context, Dqn_CPPBuildMode mode) { - if (!Dqn_OS_DirMake(build_context.build_dir)) { + if (!Dqn_OS_MakeDir(build_context.build_dir)) { Dqn_Log_ErrorF("Failed to make build dir '%.*s'", DQN_STR_FMT(build_context.build_dir)); exit(-1); } diff --git a/dqn_docs.cpp b/dqn_docs.cpp index 219862e..c269bca 100644 --- a/dqn_docs.cpp +++ b/dqn_docs.cpp @@ -238,49 +238,81 @@ void Dqn_Docs_Demo() // 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 + // Error sinks are a way of accumulating errors from API calls related or + // unrelated into 1 unified error handling pattern. The implemenation of a + // sink requires 2 fundamental design constraints on the APIs supporting + // this pattern. // // 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. + // into a sink which save the error code and message of the first error + // encountered and can be checked later. // // 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. + // Consider the following example demonstrating a conventional error + // handling approach (error values by return/sentinel values) and error + // handling using error-proof and pipelining. // (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); + Dqn_OSFile *file = Dqn_OS_FileOpen("/path/to/file", ...); + if (file) { + if (!Dqn_OS_FileWrite(file, "abc")) { + // Error handling! + } + Dnq_OS_FileClose(file); + } else { + // Error handling! + } #endif - // (B) Error handling using pipelining and and error proof APIs + // (B) Error handling using pipelining and and error proof APIs. APIs that + // produce errors take in the error sink as a parameter. 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) { + Dqn_ErrorSink *error = Dqn_ErrorSink_Begin(); + Dqn_OSFile file = Dqn_OS_FileOpen(DQN_STR8("/path/to/file"), Dqn_OSFileOpen_OpenIfExist, Dqn_OSFileAccess_ReadWrite, error); + Dqn_OS_FileWrite(&file, DQN_STR8("abc"), error); + Dqn_OS_FileClose(&file); + if (Dqn_ErrorSink_EndAndLogErrorF(error, "Failed to write to file")) { // 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. + // Pipeling and error-proof APIs lets you write sequence of instructions and + // defer error checking until it is convenient or necessary. Functions are + // *guaranteed* to return an object that is usable. There are no hidden + // exceptions to be thrown. Functions may opt to still return error values + // by way of return values thereby *not* precluding the ability to check + // every API call either. + // + // Ultimately, this error handling approach gives more flexibility on the + // manner in how errors are handled with less code. + // + // Error sinks can nest begin and end statements. This will open a new scope + // whereby the current captured error pushed onto a stack and the sink will + // be populated by the first error encountered in that scope. + + if (0) { + Dqn_ErrorSink *error = Dqn_ErrorSink_Begin(); + Dqn_OSFile file = Dqn_OS_FileOpen(DQN_STR8("/path/to/file"), Dqn_OSFileOpen_OpenIfExist, Dqn_OSFileAccess_ReadWrite, error); + Dqn_OS_FileWrite(&file, DQN_STR8("abc"), error); + Dqn_OS_FileClose(&file); + + { + // NOTE: My error sinks are thread-local, so the returned 'error' is + // the same as the 'error' value above. + Dqn_ErrorSink_Begin(); + Dqn_OS_WriteAll(DQN_STR8("/path/to/another/file"), DQN_STR8("123"), error); + Dqn_ErrorSink_EndAndLogErrorF(error, "Failed to write to another file"); + } + + if (Dqn_ErrorSink_EndAndLogErrorF(error, "Failed to write to file")) { + // Do error handling! + } + } // NOTE: Dqn_FStr8_Max ///////////////////////////////////////////////////////////////////////// // diff --git a/dqn_os.cpp b/dqn_os.cpp index 557e92f..59ac5a3 100644 --- a/dqn_os.cpp +++ b/dqn_os.cpp @@ -148,38 +148,74 @@ 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_ErrorSink *error) +DQN_API bool Dqn_OS_FileWrite(Dqn_OSFile *file, Dqn_Str8 buffer, Dqn_ErrorSink *error) { - bool result = Dqn_OS_WriteFileBuffer(file, buffer.data, buffer.size, error); + bool result = Dqn_OS_FileWritePtr(file, buffer.data, buffer.size, error); return result; } -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_FileWriteFV(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, error); + result = Dqn_OS_FileWritePtr(file, buffer.data, buffer.size, error); return result; } -DQN_API bool Dqn_OS_WriteFileF(Dqn_OSFile *file, Dqn_ErrorSink *error, DQN_FMT_ATTRIB char const *fmt, ...) +DQN_API bool Dqn_OS_FileWriteF(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, error, fmt, args); + bool result = Dqn_OS_FileWriteFV(file, error, fmt, args); va_end(args); return result; } // NOTE: R/W Entire File /////////////////////////////////////////////////////////////////////////// +DQN_API Dqn_Str8 Dqn_OS_ReadAll(Dqn_Str8 path, Dqn_Arena *arena, Dqn_ErrorSink *error) +{ + Dqn_Str8 result = {}; + if (!arena) + return result; + + // NOTE: Query file size + allocate buffer ///////////////////////////////////////////////////// + Dqn_OSPathInfo path_info = Dqn_OS_PathInfo(path); + if (!path_info.exists) { + Dqn_ErrorSink_MakeF(error, 1, "File does not exist/could not be queried for reading '%.*s'", DQN_STR_FMT(path)); + return result; + } + + Dqn_ArenaTempMem temp_mem = Dqn_Arena_TempMemBegin(arena); + result = Dqn_Str8_Alloc(arena, path_info.size, Dqn_ZeroMem_No); + if (!Dqn_Str8_HasData(result)) { + Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); + Dqn_Str8 buffer_size_str8 = Dqn_U64ToByteSizeStr8(scratch.arena, path_info.size, Dqn_U64ByteSizeType_Auto); + Dqn_ErrorSink_MakeF(error, 1 /*error_code*/, "Failed to allocate %.*s for reading file '%.*s'", DQN_STR_FMT(buffer_size_str8), DQN_STR_FMT(path)); + Dqn_Arena_TempMemEnd(temp_mem); + result = {}; + return result; + } + + // NOTE: Read the file from disk /////////////////////////////////////////////////////////////// + Dqn_OSFile file = Dqn_OS_FileOpen(path, Dqn_OSFileOpen_OpenIfExist, Dqn_OSFileAccess_Read, error); + Dqn_OS_FileRead(&file, result.data, result.size, error); + Dqn_OS_FileClose(&file); + + if (error->stack->error) { + Dqn_Arena_TempMemEnd(temp_mem); + result = {}; + } + + return result; +} 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, error); - bool result = Dqn_OS_WriteFile(&file, buffer, error); - Dqn_OS_CloseFile(&file); + Dqn_OSFile file = Dqn_OS_FileOpen(path, Dqn_OSFileOpen_CreateAlways, Dqn_OSFileAccess_Write, error); + bool result = Dqn_OS_FileWrite(&file, buffer, error); + Dqn_OS_FileClose(&file); return result; } @@ -206,7 +242,7 @@ DQN_API bool Dqn_OS_WriteAllSafe(Dqn_Str8 path, Dqn_Str8 buffer, Dqn_ErrorSink * Dqn_Str8 tmp_path = Dqn_Str8_InitF(scratch.arena, "%.*s.tmp", DQN_STR_FMT(path)); if (!Dqn_OS_WriteAll(tmp_path, buffer, error)) return false; - if (!Dqn_OS_FileCopy(tmp_path, path, true /*overwrite*/, error)) + if (!Dqn_OS_CopyFile(tmp_path, path, true /*overwrite*/, error)) return false; if (!Dqn_OS_PathDelete(tmp_path)) return false; diff --git a/dqn_os.h b/dqn_os.h index 225bb07..8f4d311 100644 --- a/dqn_os.h +++ b/dqn_os.h @@ -304,18 +304,19 @@ 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_ErrorSink *error); -DQN_API bool Dqn_OS_FileMove (Dqn_Str8 src, Dqn_Str8 dest, bool overwrite, Dqn_ErrorSink *error); +DQN_API bool Dqn_OS_CopyFile (Dqn_Str8 src, Dqn_Str8 dest, bool overwrite, Dqn_ErrorSink *error); +DQN_API bool Dqn_OS_MoveFile (Dqn_Str8 src, Dqn_Str8 dest, bool overwrite, Dqn_ErrorSink *error); +DQN_API bool Dqn_OS_MakeDir (Dqn_Str8 path, 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_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); +DQN_API Dqn_OSFile Dqn_OS_FileOpen (Dqn_Str8 path, Dqn_OSFileOpen open_mode, uint32_t access, Dqn_ErrorSink *error); +DQN_API bool Dqn_OS_FileRead (Dqn_OSFile *file, void *buffer, Dqn_usize size, Dqn_ErrorSink *error); +DQN_API bool Dqn_OS_FileWritePtr(Dqn_OSFile *file, void const *data, Dqn_usize size, Dqn_ErrorSink *error); +DQN_API bool Dqn_OS_FileWrite (Dqn_OSFile *file, Dqn_Str8 buffer, Dqn_ErrorSink *error); +DQN_API bool Dqn_OS_FileWriteFV (Dqn_OSFile *file, Dqn_ErrorSink *error, DQN_FMT_ATTRIB char const *fmt, va_list args); +DQN_API bool Dqn_OS_FileWriteF (Dqn_OSFile *file, Dqn_ErrorSink *error, DQN_FMT_ATTRIB char const *fmt, ...); +DQN_API void Dqn_OS_FileClose (Dqn_OSFile *file); // NOTE: R/W Entire File /////////////////////////////////////////////////////////////////////////// DQN_API Dqn_Str8 Dqn_OS_ReadAll (Dqn_Str8 path, Dqn_Arena *arena, Dqn_ErrorSink *error); diff --git a/dqn_os_posix.cpp b/dqn_os_posix.cpp index d48cbe9..d0e64ba 100644 --- a/dqn_os_posix.cpp +++ b/dqn_os_posix.cpp @@ -230,6 +230,14 @@ DQN_API Dqn_OSPathInfo Dqn_OS_PathInfo(Dqn_Str8 path) return result; } +DQN_API bool Dqn_OS_PathDelete(Dqn_Str8 path) +{ + bool result = false; + if (Dqn_Str8_HasData(path)) + result = remove(path.data) == 0; + return result; +} + DQN_API bool Dqn_OS_FileExists(Dqn_Str8 path) { bool result = false; @@ -242,32 +250,78 @@ 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_CopyFile(Dqn_Str8 src, Dqn_Str8 dest, bool overwrite, Dqn_ErrorSink *error) { bool result = false; #if defined(DQN_PLATFORM_EMSCRIPTEN) - DQN_ASSERTF(false, "Unsupported on Emscripten because of their VFS model"); + Dqn_ErrorSink_MakeF(error, 1, "Unsupported on Emscripten because of their VFS model"); #else - int src_fd = open(src.data, O_RDONLY); - int dest_fd = open(dest.data, O_WRONLY | O_CREAT | (overwrite ? O_TRUNC : 0)); + int src_fd = open(src.data, O_RDONLY); + if (src_fd == -1) { + int error_code = errno; + Dqn_ErrorSink_MakeF(error, + error_code, + "Failed to open file '%.*s' for copying: (%d) %s", + DQN_STR_FMT(src), + error_code, + strerror(error_code)); + return result; + } + DQN_DEFER { + close(src_fd); + }; - if (src_fd != -1 && dest_fd != -1) { - struct stat stat_existing; - fstat(src_fd, &stat_existing); - ssize_t bytes_written = sendfile64(dest_fd, src_fd, 0, stat_existing.st_size); - result = (bytes_written == stat_existing.st_size); + int dest_fd = open(dest.data, O_WRONLY | O_CREAT | (overwrite ? O_TRUNC : 0)); + if (dest_fd == -1) { + int error_code = errno; + Dqn_ErrorSink_MakeF(error, + error_code, + "Failed to open file destination '%.*s' for copying to: (%d) %s", + DQN_STR_FMT(src), + error_code, + strerror(error_code)); + return result; + } + DQN_DEFER { + close(dest_fd); + }; + + struct stat stat_existing; + int fstat_result = fstat(src_fd, &stat_existing); + if (fstat_result == -1) { + int error_code = errno; + Dqn_ErrorSink_MakeF(error, + error_code, + "Failed to query file size of '%.*s' for copying: (%d) %s", + DQN_STR_FMT(src), + error_code, + strerror(error_code)); + return result; } - if (src_fd != -1) - close(src_fd); + ssize_t bytes_written = sendfile64(dest_fd, src_fd, 0, stat_existing.st_size); + result = (bytes_written == stat_existing.st_size); + if (!result) { + int error_code = errno; + Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); + Dqn_Str8 file_size_str8 = Dqn_U64ToByteSizeStr8(scratch.arena, stat_existing.st_size, Dqn_U64ByteSizeType_Auto); + Dqn_Str8 bytes_written_str8 = Dqn_U64ToByteSizeStr8(scratch.arena, bytes_written, Dqn_U64ByteSizeType_Auto); + Dqn_ErrorSink_MakeF(error, + error_code, + "Failed to copy file '%.*s' to '%.*s', we copied %.*s but the file size is %.*s: (%d) %s", + DQN_STR_FMT(src), + DQN_STR_FMT(dest), + DQN_STR_FMT(bytes_written_str8), + DQN_STR_FMT(file_size_str8), + error_code, + strerror(error_code)); + } - if (dest_fd != -1) - close(dest_fd); #endif return result; } -DQN_API bool Dqn_OS_FileMove(Dqn_Str8 src, Dqn_Str8 dest, bool overwrite) +DQN_API bool Dqn_OS_MoveFile(Dqn_Str8 src, Dqn_Str8 dest, bool overwrite, Dqn_ErrorSink *error) { // See: https://github.com/gingerBill/gb/blob/master/gb.h bool result = false; @@ -275,11 +329,21 @@ DQN_API bool Dqn_OS_FileMove(Dqn_Str8 src, Dqn_Str8 dest, bool overwrite) if (link(src.data, dest.data) == -1) { // NOTE: Link can fail if we're trying to link across different volumes // so we fall back to a binary directory. - file_moved |= Dqn_OS_FileCopy(src, dest, overwrite); + file_moved |= Dqn_OS_CopyFile(src, dest, overwrite, error); } - if (file_moved) - result = (unlink(src.data) != -1); // Remove original file + if (file_moved) { + int unlink_result = unlink(src.data); + if (unlink_result == -1) { + int error_code = errno; + Dqn_ErrorSink_MakeF(error, + error_code, + "File '%.*s' was moved but failed to be unlinked from old location: (%d) %s", + DQN_STR_FMT(src), + error_code, + strerror(error_code)); + } + } return result; } @@ -295,7 +359,7 @@ DQN_API bool Dqn_OS_DirExists(Dqn_Str8 path) return result; } -DQN_API bool Dqn_OS_DirMake(Dqn_Str8 path) +DQN_API bool Dqn_OS_MakeDir(Dqn_Str8 path) { Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); bool result = true; @@ -351,16 +415,8 @@ DQN_API bool Dqn_OS_DirMake(Dqn_Str8 path) return result; } -DQN_API bool Dqn_OS_PathDelete(Dqn_Str8 path) -{ - bool result = false; - if (Dqn_Str8_HasData(path)) - result = remove(path.data) == 0; - return result; -} - // 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_FileOpen(Dqn_Str8 path, Dqn_OSFileOpen open_mode, uint32_t access, Dqn_ErrorSink *error) { Dqn_OSFile result = {}; if (!Dqn_Str8_HasData(path) || path.size <= 0) @@ -372,18 +428,15 @@ DQN_API Dqn_OSFile Dqn_OS_OpenFile(Dqn_Str8 path, Dqn_OSFileOpen open_mode, uint } if (access & Dqn_OSFileAccess_Execute) { - result.error_size = DQN_CAST(uint16_t) Dqn_FmtBuffer3DotTruncate( - result.error, - DQN_ARRAY_UCOUNT(result.error), - "Open file failed: execute access not supported for \"%.*s\"", - DQN_STR_FMT(path)); + result.error = true; + Dqn_ErrorSink_MakeF(error, 1, "Failed to open file '%.*s': File access flag 'execute' is not supported", DQN_STR_FMT(path)); DQN_INVALID_CODE_PATH; // TODO: Not supported via fopen return result; } // NOTE: fopen interface is not as expressive as the Win32 // We will fopen the file beforehand to setup the state/check for validity - // before closing and reopening it if valid with the correct request access + // before closing and reopening it with the correct request access // permissions. { FILE *handle = nullptr; @@ -393,13 +446,10 @@ DQN_API Dqn_OSFile Dqn_OS_OpenFile(Dqn_Str8 path, Dqn_OSFileOpen open_mode, uint case Dqn_OSFileOpen_OpenAlways: handle = fopen(path.data, "a"); break; default: DQN_INVALID_CODE_PATH; break; } - if (!handle) { - result.error_size = DQN_CAST(uint16_t)Dqn_FmtBuffer3DotTruncate( - result.error, - DQN_ARRAY_UCOUNT(result.error), - "Open file failed: Could not open file in requested mode %d for \"%.*s\"", - open_mode, - DQN_STR_FMT(path)); + + if (!handle) { // TODO(doyle): FileOpen flag to string + result.error = true; + Dqn_ErrorSink_MakeF(error, 1, "Failed to open file '%.*s': File could not be opened in requested mode 'Dqn_OSFileOpen' flag %d", DQN_STR_FMT(path), open_mode); return result; } fclose(handle); @@ -416,74 +466,49 @@ DQN_API Dqn_OSFile Dqn_OS_OpenFile(Dqn_Str8 path, Dqn_OSFileOpen open_mode, uint FILE *handle = fopen(path.data, fopen_mode); if (!handle) { - result.error_size = DQN_CAST(uint16_t) Dqn_FmtBuffer3DotTruncate( - result.error, - DQN_ARRAY_UCOUNT(result.error), - "Open file failed: Could not open file in fopen mode \"%s\" for \"%.*s\"", - fopen_mode, - DQN_STR_FMT(path)); + result.error = true; + Dqn_ErrorSink_MakeF(error, 1, "Failed to open file '%.*s': File could not be opened with requested access mode 'Dqn_OSFileAccess' %d", DQN_STR_FMT(path), fopen_mode); return result; } result.handle = handle; return result; } -DQN_API bool Dqn_OS_WriteFileBuffer(Dqn_OSFile *file, void const *buffer, Dqn_usize size) +DQN_API bool Dqn_OS_FileRead(Dqn_OSFile *file, void *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; + + if (fread(buffer, size, 1, DQN_CAST(FILE *)file->handle) != 1) { + Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); + Dqn_Str8 buffer_size_str8 = Dqn_U64ToByteSizeStr8(scratch.arena, size, Dqn_U64ByteSizeType_Auto); + Dqn_ErrorSink_MakeF(error, 1, "Failed to read %.*s from file", DQN_STR_FMT(buffer_size_str8)); + return false; + } + + return true; +} + +DQN_API bool Dqn_OS_FileWritePtr(Dqn_OSFile *file, void const *buffer, Dqn_usize size, Dqn_ErrorSink *error) +{ + if (!file || !file->handle || file->error || !buffer || size <= 0) return false; bool result = fwrite(buffer, DQN_CAST(Dqn_usize)size, 1 /*count*/, DQN_CAST(FILE *)file->handle) == 1 /*count*/; + if (!result) { + Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); + Dqn_Str8 buffer_size_str8 = Dqn_U64ToByteSizeStr8(scratch.arena, size, Dqn_U64ByteSizeType_Auto); + Dqn_ErrorSink_MakeF(error, 1, "Failed to write buffer (%s) to file handle", DQN_STR_FMT(buffer_size_str8)); + } return result; } -DQN_API void Dqn_OS_CloseFile(Dqn_OSFile *file) +DQN_API void Dqn_OS_FileClose(Dqn_OSFile *file) { - if (!file || !file->handle || file->error_size) + if (!file || !file->handle || file->error) return; fclose(DQN_CAST(FILE *)file->handle); *file = {}; } - -// NOTE: R/W Entire File /////////////////////////////////////////////////////////////////////////// -DQN_API Dqn_Str8 Dqn_OS_ReadAll(Dqn_Str8 path, Dqn_Arena *arena) -{ - Dqn_Str8 result = {}; - if (!arena) - return result; - - Dqn_ArenaTempMemScope temp_mem = Dqn_ArenaTempMemScope(arena); - FILE *file_handle = fopen(path.data, "rb"); - if (!file_handle) { - Dqn_Log_ErrorF("Failed to open file '%.*s' using fopen", DQN_STR_FMT(path)); - return result; - } - DQN_DEFER { fclose(file_handle); }; - - fseek(file_handle, 0, SEEK_END); - Dqn_usize file_size = ftell(file_handle); - - if (DQN_CAST(long)(file_size) == -1L) { - Dqn_Log_ErrorF("Failed to determine '%.*s' file size using ftell", DQN_STR_FMT(path)); - return result; - } - - rewind(file_handle); - Dqn_Str8 buffer = Dqn_Str8_Alloc(arena, file_size, Dqn_ZeroMem_No); - if (!buffer.data) { - Dqn_Log_ErrorF("Failed to allocate %zu bytes to read file '%.*s'", file_size + 1, DQN_STR_FMT(path)); - return result; - } - - if (fread(buffer.data, file_size, 1, file_handle) != 1) { - Dqn_Log_ErrorF("Failed to read %zu bytes into buffer from '%.*s'", file_size, DQN_STR_FMT(path)); - return result; - } - - buffer.data[file_size] = 0; - result = buffer; - temp_mem.mem = {}; - return result; -} #endif // !defined(DQN_NO_OS_FILE_API) // NOTE: [$EXEC] Dqn_OSExec //////////////////////////////////////////////////////////////////////// diff --git a/dqn_os_win32.cpp b/dqn_os_win32.cpp index 08a1c56..1423a21 100644 --- a/dqn_os_win32.cpp +++ b/dqn_os_win32.cpp @@ -236,6 +236,22 @@ DQN_API Dqn_OSPathInfo Dqn_OS_PathInfo(Dqn_Str8 path) return result; } +DQN_API bool Dqn_OS_PathDelete(Dqn_Str8 path) +{ + bool result = false; + if (!Dqn_Str8_HasData(path)) + return result; + + Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); + Dqn_Str16 path16 = Dqn_Win_Str8ToStr16(scratch.arena, path); + if (path16.size) { + result = DeleteFileW(path16.data); + if (!result) + result = RemoveDirectoryW(path16.data); + } + return result; +} + DQN_API bool Dqn_OS_FileExists(Dqn_Str8 path) { bool result = false; @@ -254,7 +270,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_ErrorSink *error) +DQN_API bool Dqn_OS_CopyFile(Dqn_Str8 src, Dqn_Str8 dest, bool overwrite, Dqn_ErrorSink *error) { bool result = false; Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); @@ -277,7 +293,7 @@ DQN_API bool Dqn_OS_FileCopy(Dqn_Str8 src, Dqn_Str8 dest, bool overwrite, Dqn_Er return result; } -DQN_API bool Dqn_OS_FileMove(Dqn_Str8 src, Dqn_Str8 dest, bool overwrite, Dqn_ErrorSink *error) +DQN_API bool Dqn_OS_MoveFile(Dqn_Str8 src, Dqn_Str8 dest, bool overwrite, Dqn_ErrorSink *error) { bool result = false; Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); @@ -303,27 +319,7 @@ DQN_API bool Dqn_OS_FileMove(Dqn_Str8 src, Dqn_Str8 dest, bool overwrite, Dqn_Er return result; } - -DQN_API bool Dqn_OS_DirExists(Dqn_Str8 path) -{ - bool result = false; - if (!Dqn_Str8_HasData(path)) - return result; - - Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); - Dqn_Str16 path16 = Dqn_Win_Str8ToStr16(scratch.arena, path); - if (path16.size) { - WIN32_FILE_ATTRIBUTE_DATA attrib_data = {}; - if (GetFileAttributesExW(path16.data, GetFileExInfoStandard, &attrib_data)) { - result = (attrib_data.dwFileAttributes != INVALID_FILE_ATTRIBUTES) && - (attrib_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY); - } - } - - return result; -} - -DQN_API bool Dqn_OS_DirMake(Dqn_Str8 path) +DQN_API bool Dqn_OS_MakeDir(Dqn_Str8 path) { bool result = true; Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); @@ -374,7 +370,8 @@ DQN_API bool Dqn_OS_DirMake(Dqn_Str8 path) return result; } -DQN_API bool Dqn_OS_PathDelete(Dqn_Str8 path) + +DQN_API bool Dqn_OS_DirExists(Dqn_Str8 path) { bool result = false; if (!Dqn_Str8_HasData(path)) @@ -383,15 +380,18 @@ DQN_API bool Dqn_OS_PathDelete(Dqn_Str8 path) Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); Dqn_Str16 path16 = Dqn_Win_Str8ToStr16(scratch.arena, path); if (path16.size) { - result = DeleteFileW(path16.data); - if (!result) - result = RemoveDirectoryW(path16.data); + WIN32_FILE_ATTRIBUTE_DATA attrib_data = {}; + if (GetFileAttributesExW(path16.data, GetFileExInfoStandard, &attrib_data)) { + result = (attrib_data.dwFileAttributes != INVALID_FILE_ATTRIBUTES) && + (attrib_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY); + } } + return result; } // NOTE: R/W Stream API //////////////////////////////////////////////////////////////////////////// -DQN_API Dqn_OSFile Dqn_OS_OpenFile(Dqn_Str8 path, Dqn_OSFileOpen open_mode, uint32_t access, Dqn_ErrorSink *error) +DQN_API Dqn_OSFile Dqn_OS_FileOpen(Dqn_Str8 path, Dqn_OSFileOpen open_mode, uint32_t access, Dqn_ErrorSink *error) { Dqn_OSFile result = {}; if (!Dqn_Str8_HasData(path) || path.size <= 0) @@ -445,7 +445,51 @@ 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_ErrorSink *error) +DQN_API bool Dqn_OS_FileRead(Dqn_OSFile *file, void *buffer, Dqn_usize size, Dqn_ErrorSink *error) +{ + if (!file || !file->handle || file->error || !buffer || size <= 0) + return false; + + Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); + if (!DQN_CHECK(size <= (unsigned long)-1)) { + Dqn_Str8 buffer_size_str8 = Dqn_U64ToByteSizeStr8(scratch.arena, size, Dqn_U64ByteSizeType_Auto); + Dqn_ErrorSink_MakeF( + error, + 1 /*error_code*/, + "Current implementation doesn't support reading >4GiB file (requested %.*s), implement Win32 overlapped IO", + DQN_STR_FMT(buffer_size_str8)); + return false; + } + + unsigned long bytes_read = 0; + unsigned long read_result = ReadFile(/*HANDLE hFile*/ file->handle, + /*LPVOID lpBuffer*/ buffer, + /*DWORD nNumberOfBytesToRead*/ DQN_CAST(unsigned long)size, + /*LPDWORD lpNumberOfByesRead*/ &bytes_read, + /*LPOVERLAPPED lpOverlapped*/ nullptr); + if (read_result == 0) { + Dqn_WinError win_error = Dqn_Win_LastError(scratch.arena); + Dqn_ErrorSink_MakeF(error, win_error.code, "Failed to read data from file: (%u) %.*s", win_error.code, DQN_STR_FMT(win_error.msg)); + return false; + } + + if (bytes_read != size) { + 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, we read %uB but we expected %uB: (%u) %.*s", + bytes_read, + DQN_CAST(unsigned long)size, + win_error.code, + DQN_STR_FMT(win_error.msg)); + return false; + } + + return true; +} + +DQN_API bool Dqn_OS_FileWritePtr(Dqn_OSFile *file, void const *buffer, Dqn_usize size, Dqn_ErrorSink *error) { if (!file || !file->handle || file->error || !buffer || size <= 0) return false; @@ -468,89 +512,13 @@ DQN_API bool Dqn_OS_WriteFileBuffer(Dqn_OSFile *file, void const *buffer, Dqn_us return result; } -DQN_API void Dqn_OS_CloseFile(Dqn_OSFile *file) +DQN_API void Dqn_OS_FileClose(Dqn_OSFile *file) { 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_ErrorSink *error) -{ - Dqn_Str8 result = {}; - if (!arena) - return result; - - Dqn_ArenaTempMemScope temp_mem = Dqn_ArenaTempMemScope(arena); - // NOTE: Convert to UTF16 ////////////////////////////////////////////////////////////////////// - Dqn_Scratch scratch = Dqn_Scratch_Get(arena); - Dqn_Str16 path16 = Dqn_Win_Str8ToStr16(scratch.arena, path); - - // NOTE: Get the file handle /////////////////////////////////////////////////////////////////// - void *file_handle = CreateFileW(/*LPCWSTR lpFileName*/ path16.data, - /*DWORD dwDesiredAccess*/ GENERIC_READ, - /*DWORD dwShareMode*/ FILE_SHARE_READ, - /*LPSECURITY_ATTRIBUTES lpSecurityAttributes*/ nullptr, - /*DWORD dwCreationDisposition*/ OPEN_EXISTING, - /*DWORD dwFlagsAndAttributes*/ FILE_ATTRIBUTE_READONLY, - /*HANDLE hTemplateFile*/ nullptr); - if (file_handle == INVALID_HANDLE_VALUE) { - 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); }; - - // NOTE: Query the file size /////////////////////////////////////////////////////////////////// - LARGE_INTEGER win_file_size; - if (!GetFileSizeEx(file_handle, &win_file_size)) { - 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_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; - } - - // NOTE: Read the file from disk /////////////////////////////////////////////////////////////// - Dqn_Str8 buffer = Dqn_Str8_Alloc(arena, bytes_desired, Dqn_ZeroMem_No); - unsigned long bytes_read = 0; - unsigned long read_result = ReadFile(/*HANDLE hFile*/ file_handle, - /*LPVOID lpBuffer*/ buffer.data, - /*DWORD nNumberOfBytesToRead*/ bytes_desired, - /*LPDWORD lpNumberOfByesRead*/ &bytes_read, - /*LPOVERLAPPED lpOverlapped*/ nullptr); - - if (read_result == 0) { - 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 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; - } - - buffer.data[bytes_desired] = 0; - result = buffer; - temp_mem.mem = {}; - return result; -} #endif // !defined(DQN_NO_OS_FILE_API) // NOTE: [$EXEC] Dqn_OSExec //////////////////////////////////////////////////////////////////////// diff --git a/dqn_unit_tests.cpp b/dqn_unit_tests.cpp index f5f1b0e..1653730 100644 --- a/dqn_unit_tests.cpp +++ b/dqn_unit_tests.cpp @@ -698,7 +698,7 @@ static Dqn_UTest Dqn_Test_Fs() Dqn_UTest test = {}; DQN_UTEST_GROUP(test, "Dqn_Fs") { DQN_UTEST_TEST("Make directory recursive \"abcd/efgh\"") { - DQN_UTEST_ASSERTF(&test, Dqn_OS_DirMake(DQN_STR8("abcd/efgh")), "Failed to make directory"); + DQN_UTEST_ASSERTF(&test, Dqn_OS_MakeDir(DQN_STR8("abcd/efgh")), "Failed to make directory"); DQN_UTEST_ASSERTF(&test, Dqn_OS_DirExists(DQN_STR8("abcd")), "Directory was not made"); DQN_UTEST_ASSERTF(&test, Dqn_OS_DirExists(DQN_STR8("abcd/efgh")), "Subdirectory was not made"); DQN_UTEST_ASSERTF(&test, Dqn_OS_FileExists(DQN_STR8("abcd")) == false, "This function should only return true for files"); @@ -723,13 +723,13 @@ static Dqn_UTest Dqn_Test_Fs() // 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*/, nullptr); + Dqn_b32 copy_result = Dqn_OS_CopyFile(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*/, nullptr); + Dqn_b32 move_result = Dqn_OS_MoveFile(COPY_FILE, MOVE_FILE, true /*overwrite*/, nullptr); DQN_UTEST_ASSERT(&test, move_result); 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");