Implement error sink on posix layer

This commit is contained in:
doylet 2024-02-11 18:23:13 +11:00
parent 18a37024da
commit 8b2aea5b1d
9 changed files with 312 additions and 250 deletions

View File

@ -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);
} }

View File

@ -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;

View File

@ -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);
} }

View File

@ -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 /////////////////////////////////////////////////////////////////////////
// //

View File

@ -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;

View File

@ -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);

View File

@ -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 ////////////////////////////////////////////////////////////////////////

View File

@ -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 ////////////////////////////////////////////////////////////////////////

View File

@ -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");