Implement error sink on posix layer
This commit is contained in:
parent
18a37024da
commit
8b2aea5b1d
@ -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) {
|
if (lib->log_to_file && !lib->log_file.handle && !lib->log_file.error) {
|
||||||
Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr);
|
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));
|
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);
|
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_Print_StdLn(Dqn_PrintStd_Out, log_line);
|
||||||
|
|
||||||
Dqn_TicketMutex_Begin(&lib->log_file_mutex);
|
Dqn_TicketMutex_Begin(&lib->log_file_mutex);
|
||||||
Dqn_OS_WriteFile(&lib->log_file, log_line, nullptr);
|
Dqn_OS_FileWrite(&lib->log_file, log_line, nullptr);
|
||||||
Dqn_OS_WriteFile(&lib->log_file, DQN_STR8("\n"), nullptr);
|
Dqn_OS_FileWrite(&lib->log_file, DQN_STR8("\n"), nullptr);
|
||||||
Dqn_TicketMutex_End(&lib->log_file_mutex);
|
Dqn_TicketMutex_End(&lib->log_file_mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -478,7 +478,7 @@ template <typename T> Dqn_VArray<T> Dqn_VArray_Init(Dqn_usize max, uint8_t arena
|
|||||||
template <typename T> Dqn_VArray<T> Dqn_VArray_InitSlice(Dqn_Slice<T> slice, Dqn_usize max, uint8_t arena_flags)
|
template <typename T> Dqn_VArray<T> Dqn_VArray_InitSlice(Dqn_Slice<T> slice, Dqn_usize max, uint8_t arena_flags)
|
||||||
{
|
{
|
||||||
Dqn_usize real_max = DQN_MAX(slice.size, max);
|
Dqn_usize real_max = DQN_MAX(slice.size, max);
|
||||||
Dqn_VArray<T> result = Dqn_VArray_Init(real_max, arena_flags);
|
Dqn_VArray<T> result = Dqn_VArray_Init<T>(real_max, arena_flags);
|
||||||
if (Dqn_VArray_IsValid(&result))
|
if (Dqn_VArray_IsValid(&result))
|
||||||
Dqn_VArray_AddArray(&result, slice.data, slice.size);
|
Dqn_VArray_AddArray(&result, slice.data, slice.size);
|
||||||
return result;
|
return result;
|
||||||
|
@ -179,7 +179,7 @@ DQN_API Dqn_CPPBuildAsyncResult Dqn_CPPBuild_Async(Dqn_CPPBuildContext build_con
|
|||||||
if (!cmd_line.size)
|
if (!cmd_line.size)
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
if (!Dqn_OS_DirMake(build_context.build_dir)) {
|
if (!Dqn_OS_MakeDir(build_context.build_dir)) {
|
||||||
result.status = Dqn_CPPBuildStatus_BuildDirectoryFailedToBeMade;
|
result.status = Dqn_CPPBuildStatus_BuildDirectoryFailedToBeMade;
|
||||||
return result;
|
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)
|
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));
|
Dqn_Log_ErrorF("Failed to make build dir '%.*s'", DQN_STR_FMT(build_context.build_dir));
|
||||||
exit(-1);
|
exit(-1);
|
||||||
}
|
}
|
||||||
|
76
dqn_docs.cpp
76
dqn_docs.cpp
@ -238,49 +238,81 @@ void Dqn_Docs_Demo()
|
|||||||
|
|
||||||
// NOTE: Dqn_ErrorSink /////////////////////////////////////////////////////////////////////////
|
// NOTE: Dqn_ErrorSink /////////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
// A thread-local data structure that collects all errors emitted by APIs
|
// Error sinks are a way of accumulating errors from API calls related or
|
||||||
// into one unified structure. This library has 2 core tenets when handling
|
// unrelated into 1 unified error handling pattern. The implemenation of a
|
||||||
// errors
|
// sink requires 2 fundamental design constraints on the APIs supporting
|
||||||
|
// this pattern.
|
||||||
//
|
//
|
||||||
// 1. Pipelining of errors
|
// 1. Pipelining of errors
|
||||||
// Errors emitted over the course of several API calls are accumulated
|
// Errors emitted over the course of several API calls are accumulated
|
||||||
// into a thread-local sink which save the error code and message
|
// into a sink which save the error code and message of the first error
|
||||||
// of the first error encountered.
|
// encountered and can be checked later.
|
||||||
//
|
//
|
||||||
// 2. Error proof APIs
|
// 2. Error proof APIs
|
||||||
// Functions that produce errors must return objects/handles that are
|
// Functions that produce errors must return objects/handles that are
|
||||||
// marked to trigger no-ops used in subsequent functions dependent on it.
|
// 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
|
// Consider the following example demonstrating a conventional error
|
||||||
// checking until the end of a sequence of actions. Consider the following
|
// handling approach (error values by return/sentinel values) and error
|
||||||
// example demonstrating the 2 approaches.
|
// handling using error-proof and pipelining.
|
||||||
|
|
||||||
// (A) Conventional error checking patterns using return/sentinel values
|
// (A) Conventional error checking patterns using return/sentinel values
|
||||||
#if 0
|
#if 0
|
||||||
FileHandle *file = OpenFile("/path/to/file");
|
Dqn_OSFile *file = Dqn_OS_FileOpen("/path/to/file", ...);
|
||||||
if (!file)
|
if (file) {
|
||||||
|
if (!Dqn_OS_FileWrite(file, "abc")) {
|
||||||
// Error handling!
|
// Error handling!
|
||||||
if (!WriteFile(file, "abc"))
|
}
|
||||||
|
Dnq_OS_FileClose(file);
|
||||||
|
} else {
|
||||||
// Error handling!
|
// Error handling!
|
||||||
CloseFile(file);
|
}
|
||||||
#endif
|
#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) {
|
if (0) {
|
||||||
Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr);
|
|
||||||
Dqn_ErrorSink *error = Dqn_ErrorSink_Begin();
|
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_OSFile file = Dqn_OS_FileOpen(DQN_STR8("/path/to/file"), Dqn_OSFileOpen_OpenIfExist, Dqn_OSFileAccess_ReadWrite, error);
|
||||||
Dqn_OS_WriteFile(&file, DQN_STR8("abc"), error);
|
Dqn_OS_FileWrite(&file, DQN_STR8("abc"), error);
|
||||||
Dqn_OS_CloseFile(&file);
|
Dqn_OS_FileClose(&file);
|
||||||
|
if (Dqn_ErrorSink_EndAndLogErrorF(error, "Failed to write to file")) {
|
||||||
Dqn_ErrorSinkNode error_node = Dqn_ErrorSink_End(scratch.arena, error);
|
|
||||||
if (error_node.error) {
|
|
||||||
// Do error handling!
|
// 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 /////////////////////////////////////////////////////////////////////////
|
// NOTE: Dqn_FStr8_Max /////////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
|
56
dqn_os.cpp
56
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)
|
#if !defined(DQN_NO_OS_FILE_API)
|
||||||
// NOTE: [$FILE] Dqn_OSPathInfo/File ///////////////////////////////////////////////////////////////
|
// 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;
|
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;
|
bool result = false;
|
||||||
if (!file || !fmt)
|
if (!file || !fmt)
|
||||||
return result;
|
return result;
|
||||||
Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr);
|
Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr);
|
||||||
Dqn_Str8 buffer = Dqn_Str8_InitFV(scratch.arena, fmt, args);
|
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;
|
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_list args;
|
||||||
va_start(args, fmt);
|
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);
|
va_end(args);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: R/W Entire 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;
|
||||||
|
|
||||||
|
// 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_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);
|
Dqn_OSFile file = Dqn_OS_FileOpen(path, Dqn_OSFileOpen_CreateAlways, Dqn_OSFileAccess_Write, error);
|
||||||
bool result = Dqn_OS_WriteFile(&file, buffer, error);
|
bool result = Dqn_OS_FileWrite(&file, buffer, error);
|
||||||
Dqn_OS_CloseFile(&file);
|
Dqn_OS_FileClose(&file);
|
||||||
return result;
|
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));
|
Dqn_Str8 tmp_path = Dqn_Str8_InitF(scratch.arena, "%.*s.tmp", DQN_STR_FMT(path));
|
||||||
if (!Dqn_OS_WriteAll(tmp_path, buffer, error))
|
if (!Dqn_OS_WriteAll(tmp_path, buffer, error))
|
||||||
return false;
|
return false;
|
||||||
if (!Dqn_OS_FileCopy(tmp_path, path, true /*overwrite*/, error))
|
if (!Dqn_OS_CopyFile(tmp_path, path, true /*overwrite*/, error))
|
||||||
return false;
|
return false;
|
||||||
if (!Dqn_OS_PathDelete(tmp_path))
|
if (!Dqn_OS_PathDelete(tmp_path))
|
||||||
return false;
|
return false;
|
||||||
|
19
dqn_os.h
19
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 Dqn_OSPathInfo Dqn_OS_PathInfo (Dqn_Str8 path);
|
||||||
DQN_API bool Dqn_OS_PathDelete(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_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_CopyFile (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_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_DirExists (Dqn_Str8 path, Dqn_ErrorSink *error);
|
||||||
DQN_API bool Dqn_OS_DirMake (Dqn_Str8 path, Dqn_ErrorSink *error);
|
|
||||||
|
|
||||||
// NOTE: R/W Stream API ////////////////////////////////////////////////////////////////////////////
|
// 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_API bool Dqn_OS_WriteFileBuffer(Dqn_OSFile *file, void const *data, Dqn_usize size, 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_WriteFile (Dqn_OSFile *file, Dqn_Str8 buffer, 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_WriteFileFV (Dqn_OSFile *file, Dqn_ErrorSink *error, DQN_FMT_ATTRIB char const *fmt, va_list args);
|
DQN_API bool Dqn_OS_FileWrite (Dqn_OSFile *file, Dqn_Str8 buffer, Dqn_ErrorSink *error);
|
||||||
DQN_API bool Dqn_OS_WriteFileF (Dqn_OSFile *file, Dqn_ErrorSink *error, DQN_FMT_ATTRIB char const *fmt, ...);
|
DQN_API bool Dqn_OS_FileWriteFV (Dqn_OSFile *file, Dqn_ErrorSink *error, DQN_FMT_ATTRIB char const *fmt, va_list args);
|
||||||
DQN_API void Dqn_OS_CloseFile (Dqn_OSFile *file);
|
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 ///////////////////////////////////////////////////////////////////////////
|
// NOTE: R/W Entire File ///////////////////////////////////////////////////////////////////////////
|
||||||
DQN_API Dqn_Str8 Dqn_OS_ReadAll (Dqn_Str8 path, Dqn_Arena *arena, Dqn_ErrorSink *error);
|
DQN_API Dqn_Str8 Dqn_OS_ReadAll (Dqn_Str8 path, Dqn_Arena *arena, Dqn_ErrorSink *error);
|
||||||
|
203
dqn_os_posix.cpp
203
dqn_os_posix.cpp
@ -230,6 +230,14 @@ DQN_API Dqn_OSPathInfo Dqn_OS_PathInfo(Dqn_Str8 path)
|
|||||||
return result;
|
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)
|
DQN_API bool Dqn_OS_FileExists(Dqn_Str8 path)
|
||||||
{
|
{
|
||||||
bool result = false;
|
bool result = false;
|
||||||
@ -242,32 +250,78 @@ DQN_API bool Dqn_OS_FileExists(Dqn_Str8 path)
|
|||||||
return result;
|
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;
|
bool result = false;
|
||||||
#if defined(DQN_PLATFORM_EMSCRIPTEN)
|
#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
|
#else
|
||||||
int src_fd = open(src.data, O_RDONLY);
|
int src_fd = open(src.data, O_RDONLY);
|
||||||
int dest_fd = open(dest.data, O_WRONLY | O_CREAT | (overwrite ? O_TRUNC : 0));
|
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);
|
||||||
|
};
|
||||||
|
|
||||||
|
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);
|
||||||
|
};
|
||||||
|
|
||||||
if (src_fd != -1 && dest_fd != -1) {
|
|
||||||
struct stat stat_existing;
|
struct stat stat_existing;
|
||||||
fstat(src_fd, &stat_existing);
|
int fstat_result = fstat(src_fd, &stat_existing);
|
||||||
ssize_t bytes_written = sendfile64(dest_fd, src_fd, 0, stat_existing.st_size);
|
if (fstat_result == -1) {
|
||||||
result = (bytes_written == stat_existing.st_size);
|
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)
|
ssize_t bytes_written = sendfile64(dest_fd, src_fd, 0, stat_existing.st_size);
|
||||||
close(src_fd);
|
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
|
#endif
|
||||||
return result;
|
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
|
// See: https://github.com/gingerBill/gb/blob/master/gb.h
|
||||||
bool result = false;
|
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) {
|
if (link(src.data, dest.data) == -1) {
|
||||||
// NOTE: Link can fail if we're trying to link across different volumes
|
// NOTE: Link can fail if we're trying to link across different volumes
|
||||||
// so we fall back to a binary directory.
|
// 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)
|
if (file_moved) {
|
||||||
result = (unlink(src.data) != -1); // Remove original file
|
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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -295,7 +359,7 @@ DQN_API bool Dqn_OS_DirExists(Dqn_Str8 path)
|
|||||||
return result;
|
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);
|
Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr);
|
||||||
bool result = true;
|
bool result = true;
|
||||||
@ -351,16 +415,8 @@ DQN_API bool Dqn_OS_DirMake(Dqn_Str8 path)
|
|||||||
return result;
|
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 ////////////////////////////////////////////////////////////////////////////
|
// 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 = {};
|
Dqn_OSFile result = {};
|
||||||
if (!Dqn_Str8_HasData(path) || path.size <= 0)
|
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) {
|
if (access & Dqn_OSFileAccess_Execute) {
|
||||||
result.error_size = DQN_CAST(uint16_t) Dqn_FmtBuffer3DotTruncate(
|
result.error = true;
|
||||||
result.error,
|
Dqn_ErrorSink_MakeF(error, 1, "Failed to open file '%.*s': File access flag 'execute' is not supported", DQN_STR_FMT(path));
|
||||||
DQN_ARRAY_UCOUNT(result.error),
|
|
||||||
"Open file failed: execute access not supported for \"%.*s\"",
|
|
||||||
DQN_STR_FMT(path));
|
|
||||||
DQN_INVALID_CODE_PATH; // TODO: Not supported via fopen
|
DQN_INVALID_CODE_PATH; // TODO: Not supported via fopen
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: fopen interface is not as expressive as the Win32
|
// NOTE: fopen interface is not as expressive as the Win32
|
||||||
// We will fopen the file beforehand to setup the state/check for validity
|
// 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.
|
// permissions.
|
||||||
{
|
{
|
||||||
FILE *handle = nullptr;
|
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;
|
case Dqn_OSFileOpen_OpenAlways: handle = fopen(path.data, "a"); break;
|
||||||
default: DQN_INVALID_CODE_PATH; break;
|
default: DQN_INVALID_CODE_PATH; break;
|
||||||
}
|
}
|
||||||
if (!handle) {
|
|
||||||
result.error_size = DQN_CAST(uint16_t)Dqn_FmtBuffer3DotTruncate(
|
if (!handle) { // TODO(doyle): FileOpen flag to string
|
||||||
result.error,
|
result.error = true;
|
||||||
DQN_ARRAY_UCOUNT(result.error),
|
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);
|
||||||
"Open file failed: Could not open file in requested mode %d for \"%.*s\"",
|
|
||||||
open_mode,
|
|
||||||
DQN_STR_FMT(path));
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
fclose(handle);
|
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);
|
FILE *handle = fopen(path.data, fopen_mode);
|
||||||
if (!handle) {
|
if (!handle) {
|
||||||
result.error_size = DQN_CAST(uint16_t) Dqn_FmtBuffer3DotTruncate(
|
result.error = true;
|
||||||
result.error,
|
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);
|
||||||
DQN_ARRAY_UCOUNT(result.error),
|
|
||||||
"Open file failed: Could not open file in fopen mode \"%s\" for \"%.*s\"",
|
|
||||||
fopen_mode,
|
|
||||||
DQN_STR_FMT(path));
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
result.handle = handle;
|
result.handle = handle;
|
||||||
return result;
|
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;
|
return false;
|
||||||
bool result = fwrite(buffer, DQN_CAST(Dqn_usize)size, 1 /*count*/, DQN_CAST(FILE *)file->handle) == 1 /*count*/;
|
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;
|
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;
|
return;
|
||||||
fclose(DQN_CAST(FILE *)file->handle);
|
fclose(DQN_CAST(FILE *)file->handle);
|
||||||
*file = {};
|
*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)
|
#endif // !defined(DQN_NO_OS_FILE_API)
|
||||||
|
|
||||||
// NOTE: [$EXEC] Dqn_OSExec ////////////////////////////////////////////////////////////////////////
|
// NOTE: [$EXEC] Dqn_OSExec ////////////////////////////////////////////////////////////////////////
|
||||||
|
180
dqn_os_win32.cpp
180
dqn_os_win32.cpp
@ -236,6 +236,22 @@ DQN_API Dqn_OSPathInfo Dqn_OS_PathInfo(Dqn_Str8 path)
|
|||||||
return result;
|
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)
|
DQN_API bool Dqn_OS_FileExists(Dqn_Str8 path)
|
||||||
{
|
{
|
||||||
bool result = false;
|
bool result = false;
|
||||||
@ -254,7 +270,7 @@ DQN_API bool Dqn_OS_FileExists(Dqn_Str8 path)
|
|||||||
return result;
|
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;
|
bool result = false;
|
||||||
Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr);
|
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;
|
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;
|
bool result = false;
|
||||||
Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr);
|
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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DQN_API bool Dqn_OS_MakeDir(Dqn_Str8 path)
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
bool result = true;
|
bool result = true;
|
||||||
Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr);
|
Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr);
|
||||||
@ -374,7 +370,8 @@ DQN_API bool Dqn_OS_DirMake(Dqn_Str8 path)
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
DQN_API bool Dqn_OS_PathDelete(Dqn_Str8 path)
|
|
||||||
|
DQN_API bool Dqn_OS_DirExists(Dqn_Str8 path)
|
||||||
{
|
{
|
||||||
bool result = false;
|
bool result = false;
|
||||||
if (!Dqn_Str8_HasData(path))
|
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_Scratch scratch = Dqn_Scratch_Get(nullptr);
|
||||||
Dqn_Str16 path16 = Dqn_Win_Str8ToStr16(scratch.arena, path);
|
Dqn_Str16 path16 = Dqn_Win_Str8ToStr16(scratch.arena, path);
|
||||||
if (path16.size) {
|
if (path16.size) {
|
||||||
result = DeleteFileW(path16.data);
|
WIN32_FILE_ATTRIBUTE_DATA attrib_data = {};
|
||||||
if (!result)
|
if (GetFileAttributesExW(path16.data, GetFileExInfoStandard, &attrib_data)) {
|
||||||
result = RemoveDirectoryW(path16.data);
|
result = (attrib_data.dwFileAttributes != INVALID_FILE_ATTRIBUTES) &&
|
||||||
|
(attrib_data.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: R/W Stream API ////////////////////////////////////////////////////////////////////////////
|
// 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 = {};
|
Dqn_OSFile result = {};
|
||||||
if (!Dqn_Str8_HasData(path) || path.size <= 0)
|
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;
|
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)
|
if (!file || !file->handle || file->error || !buffer || size <= 0)
|
||||||
return false;
|
return false;
|
||||||
@ -468,89 +512,13 @@ DQN_API bool Dqn_OS_WriteFileBuffer(Dqn_OSFile *file, void const *buffer, Dqn_us
|
|||||||
return result;
|
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)
|
if (!file || !file->handle || file->error)
|
||||||
return;
|
return;
|
||||||
CloseHandle(file->handle);
|
CloseHandle(file->handle);
|
||||||
*file = {};
|
*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)
|
#endif // !defined(DQN_NO_OS_FILE_API)
|
||||||
|
|
||||||
// NOTE: [$EXEC] Dqn_OSExec ////////////////////////////////////////////////////////////////////////
|
// NOTE: [$EXEC] Dqn_OSExec ////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -698,7 +698,7 @@ static Dqn_UTest Dqn_Test_Fs()
|
|||||||
Dqn_UTest test = {};
|
Dqn_UTest test = {};
|
||||||
DQN_UTEST_GROUP(test, "Dqn_Fs") {
|
DQN_UTEST_GROUP(test, "Dqn_Fs") {
|
||||||
DQN_UTEST_TEST("Make directory recursive \"abcd/efgh\"") {
|
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")), "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_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");
|
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
|
// NOTE: Copy step
|
||||||
Dqn_Str8 const COPY_FILE = DQN_STR8("dqn_test_file_copy");
|
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, copy_result);
|
||||||
DQN_UTEST_ASSERT(&test, Dqn_OS_FileExists(COPY_FILE));
|
DQN_UTEST_ASSERT(&test, Dqn_OS_FileExists(COPY_FILE));
|
||||||
|
|
||||||
// NOTE: Move step
|
// NOTE: Move step
|
||||||
Dqn_Str8 const MOVE_FILE = DQN_STR8("dqn_test_file_move");
|
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, 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");
|
DQN_UTEST_ASSERTF(&test, Dqn_OS_FileExists(COPY_FILE) == false, "Moving a file should remove the original");
|
||||||
|
Loading…
Reference in New Issue
Block a user