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

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)
{
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))
Dqn_VArray_AddArray(&result, slice.data, slice.size);
return result;

View File

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

View File

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

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

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

View File

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

View File

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

View File

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