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) { | ||||
|         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); | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -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; | ||||
|  | ||||
| @ -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); | ||||
|     } | ||||
|  | ||||
							
								
								
									
										82
									
								
								dqn_docs.cpp
									
									
									
									
									
								
							
							
						
						
									
										82
									
								
								dqn_docs.cpp
									
									
									
									
									
								
							| @ -238,49 +238,81 @@ void Dqn_Docs_Demo() | ||||
| 
 | ||||
|     // NOTE: Dqn_ErrorSink /////////////////////////////////////////////////////////////////////////
 | ||||
|     //
 | ||||
|     // A thread-local data structure that collects all errors emitted by APIs
 | ||||
|     // into one unified structure. This library has 2 core tenets when handling
 | ||||
|     // errors
 | ||||
|     // Error sinks are a way of accumulating errors from API calls related or
 | ||||
|     // unrelated into 1 unified error handling pattern. The implemenation of a
 | ||||
|     // sink requires 2 fundamental design constraints on the APIs supporting
 | ||||
|     // this pattern.
 | ||||
|     //
 | ||||
|     // 1. Pipelining of errors
 | ||||
|     //    Errors emitted over the course of several API calls are accumulated
 | ||||
|     //    into a thread-local sink which save the error code and message
 | ||||
|     //    of the first error encountered.
 | ||||
|     //    into a sink which save the error code and message of the first error
 | ||||
|     //    encountered and can be checked later.
 | ||||
|     //
 | ||||
|     // 2. Error proof APIs
 | ||||
|     //    Functions that produce errors must return objects/handles that are
 | ||||
|     //    marked to trigger no-ops used in subsequent functions dependent on it.
 | ||||
|     //
 | ||||
|     // Together this allows end-users of APIs to chain calls and defer error
 | ||||
|     // checking until the end of a sequence of actions. Consider the following
 | ||||
|     // example demonstrating the 2 approaches.
 | ||||
|     // Consider the following example demonstrating a conventional error
 | ||||
|     // handling approach (error values by return/sentinel values) and error
 | ||||
|     // handling using error-proof and pipelining.
 | ||||
| 
 | ||||
|     // (A) Conventional error checking patterns using return/sentinel values
 | ||||
|     #if 0 | ||||
|     FileHandle *file = OpenFile("/path/to/file"); | ||||
|     if (!file) | ||||
|         // Error handling!
 | ||||
|     if (!WriteFile(file, "abc")) | ||||
|         // Error handling!
 | ||||
|     CloseFile(file); | ||||
|         Dqn_OSFile *file = Dqn_OS_FileOpen("/path/to/file", ...); | ||||
|         if (file) { | ||||
|             if (!Dqn_OS_FileWrite(file, "abc")) { | ||||
|                 // Error handling!
 | ||||
|             } | ||||
|             Dnq_OS_FileClose(file); | ||||
|         } else { | ||||
|             // Error handling!
 | ||||
|         } | ||||
|     #endif | ||||
| 
 | ||||
|     // (B) Error handling using pipelining and and error proof APIs
 | ||||
|     // (B) Error handling using pipelining and and error proof APIs. APIs that
 | ||||
|     // produce errors take in the error sink as a parameter.
 | ||||
|     if (0) { | ||||
|         Dqn_Scratch    scratch = Dqn_Scratch_Get(nullptr); | ||||
|         Dqn_ErrorSink *error   = Dqn_ErrorSink_Begin(); | ||||
|         Dqn_OSFile     file    = Dqn_OS_OpenFile(DQN_STR8("/path/to/file"), Dqn_OSFileOpen_OpenIfExist, Dqn_OSFileAccess_ReadWrite, error); | ||||
|         Dqn_OS_WriteFile(&file, DQN_STR8("abc"), error); | ||||
|         Dqn_OS_CloseFile(&file); | ||||
| 
 | ||||
|         Dqn_ErrorSinkNode error_node = Dqn_ErrorSink_End(scratch.arena, error); | ||||
|         if (error_node.error) { | ||||
|         Dqn_ErrorSink *error = Dqn_ErrorSink_Begin(); | ||||
|         Dqn_OSFile     file  = Dqn_OS_FileOpen(DQN_STR8("/path/to/file"), Dqn_OSFileOpen_OpenIfExist, Dqn_OSFileAccess_ReadWrite, error); | ||||
|         Dqn_OS_FileWrite(&file, DQN_STR8("abc"), error); | ||||
|         Dqn_OS_FileClose(&file); | ||||
|         if (Dqn_ErrorSink_EndAndLogErrorF(error, "Failed to write to file")) { | ||||
|             // Do error handling!
 | ||||
|             Dqn_Log_ErrorF("%.*s", DQN_STR_FMT(error_node.msg)); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // TODO(doyle): Integrate more into the codebase and provide a concrete example.
 | ||||
|     // Pipeling and error-proof APIs lets you write sequence of instructions and
 | ||||
|     // defer error checking until it is convenient or necessary. Functions are
 | ||||
|     // *guaranteed* to return an object that is usable. There are no hidden
 | ||||
|     // exceptions to be thrown. Functions may opt to still return error values
 | ||||
|     // by way of return values thereby *not* precluding the ability to check
 | ||||
|     // every API call either.
 | ||||
|     //
 | ||||
|     // Ultimately, this error handling approach gives more flexibility on the
 | ||||
|     // manner in how errors are handled with less code.
 | ||||
|     //
 | ||||
|     // Error sinks can nest begin and end statements. This will open a new scope
 | ||||
|     // whereby the current captured error pushed onto a stack and the sink will
 | ||||
|     // be populated by the first error encountered in that scope.
 | ||||
| 
 | ||||
|     if (0) { | ||||
|         Dqn_ErrorSink *error = Dqn_ErrorSink_Begin(); | ||||
|         Dqn_OSFile     file  = Dqn_OS_FileOpen(DQN_STR8("/path/to/file"), Dqn_OSFileOpen_OpenIfExist, Dqn_OSFileAccess_ReadWrite, error); | ||||
|         Dqn_OS_FileWrite(&file, DQN_STR8("abc"), error); | ||||
|         Dqn_OS_FileClose(&file); | ||||
| 
 | ||||
|         { | ||||
|             // NOTE: My error sinks are thread-local, so the returned 'error' is
 | ||||
|             // the same as the 'error' value above.
 | ||||
|             Dqn_ErrorSink_Begin(); | ||||
|             Dqn_OS_WriteAll(DQN_STR8("/path/to/another/file"), DQN_STR8("123"), error); | ||||
|             Dqn_ErrorSink_EndAndLogErrorF(error, "Failed to write to another file"); | ||||
|         } | ||||
| 
 | ||||
|         if (Dqn_ErrorSink_EndAndLogErrorF(error, "Failed to write to file")) { | ||||
|             // Do error handling!
 | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // NOTE: Dqn_FStr8_Max /////////////////////////////////////////////////////////////////////////
 | ||||
|     //
 | ||||
|  | ||||
							
								
								
									
										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) | ||||
| // 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; | ||||
|  | ||||
							
								
								
									
										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 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); | ||||
|  | ||||
							
								
								
									
										207
									
								
								dqn_os_posix.cpp
									
									
									
									
									
								
							
							
						
						
									
										207
									
								
								dqn_os_posix.cpp
									
									
									
									
									
								
							| @ -230,6 +230,14 @@ DQN_API Dqn_OSPathInfo Dqn_OS_PathInfo(Dqn_Str8 path) | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| DQN_API bool Dqn_OS_PathDelete(Dqn_Str8 path) | ||||
| { | ||||
|     bool result = false; | ||||
|     if (Dqn_Str8_HasData(path)) | ||||
|         result = remove(path.data) == 0; | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| DQN_API bool Dqn_OS_FileExists(Dqn_Str8 path) | ||||
| { | ||||
|     bool result = false; | ||||
| @ -242,32 +250,78 @@ DQN_API bool Dqn_OS_FileExists(Dqn_Str8 path) | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| DQN_API bool Dqn_OS_FileCopy(Dqn_Str8 src, Dqn_Str8 dest, bool overwrite) | ||||
| DQN_API bool Dqn_OS_CopyFile(Dqn_Str8 src, Dqn_Str8 dest, bool overwrite, Dqn_ErrorSink *error) | ||||
| { | ||||
|     bool result = false; | ||||
|     #if defined(DQN_PLATFORM_EMSCRIPTEN) | ||||
|     DQN_ASSERTF(false, "Unsupported on Emscripten because of their VFS model"); | ||||
|     Dqn_ErrorSink_MakeF(error, 1, "Unsupported on Emscripten because of their VFS model"); | ||||
|     #else | ||||
|     int src_fd  = open(src.data, O_RDONLY); | ||||
|     int dest_fd = open(dest.data, O_WRONLY | O_CREAT | (overwrite ? O_TRUNC : 0)); | ||||
|     int src_fd = open(src.data, O_RDONLY); | ||||
|     if (src_fd == -1) { | ||||
|         int error_code = errno; | ||||
|         Dqn_ErrorSink_MakeF(error, | ||||
|                             error_code, | ||||
|                             "Failed to open file '%.*s' for copying: (%d) %s", | ||||
|                             DQN_STR_FMT(src), | ||||
|                             error_code, | ||||
|                             strerror(error_code)); | ||||
|         return result; | ||||
|     } | ||||
|     DQN_DEFER { | ||||
|         close(src_fd); | ||||
|     }; | ||||
| 
 | ||||
|     if (src_fd != -1 && dest_fd != -1) { | ||||
|         struct stat stat_existing; | ||||
|         fstat(src_fd, &stat_existing); | ||||
|         ssize_t bytes_written = sendfile64(dest_fd, src_fd, 0, stat_existing.st_size); | ||||
|         result = (bytes_written == stat_existing.st_size); | ||||
|     int dest_fd = open(dest.data, O_WRONLY | O_CREAT | (overwrite ? O_TRUNC : 0)); | ||||
|     if (dest_fd == -1) { | ||||
|         int error_code = errno; | ||||
|         Dqn_ErrorSink_MakeF(error, | ||||
|                             error_code, | ||||
|                             "Failed to open file destination '%.*s' for copying to: (%d) %s", | ||||
|                             DQN_STR_FMT(src), | ||||
|                             error_code, | ||||
|                             strerror(error_code)); | ||||
|         return result; | ||||
|     } | ||||
|     DQN_DEFER { | ||||
|         close(dest_fd); | ||||
|     }; | ||||
| 
 | ||||
|     struct stat stat_existing; | ||||
|     int fstat_result = fstat(src_fd, &stat_existing); | ||||
|     if (fstat_result == -1) { | ||||
|         int error_code = errno; | ||||
|         Dqn_ErrorSink_MakeF(error, | ||||
|                             error_code, | ||||
|                             "Failed to query file size of '%.*s' for copying: (%d) %s", | ||||
|                             DQN_STR_FMT(src), | ||||
|                             error_code, | ||||
|                             strerror(error_code)); | ||||
|         return result; | ||||
|     } | ||||
| 
 | ||||
|     if (src_fd != -1) | ||||
|         close(src_fd); | ||||
|     ssize_t bytes_written = sendfile64(dest_fd, src_fd, 0, stat_existing.st_size); | ||||
|     result                = (bytes_written == stat_existing.st_size); | ||||
|     if (!result) { | ||||
|         int         error_code         = errno; | ||||
|         Dqn_Scratch scratch            = Dqn_Scratch_Get(nullptr); | ||||
|         Dqn_Str8    file_size_str8     = Dqn_U64ToByteSizeStr8(scratch.arena, stat_existing.st_size, Dqn_U64ByteSizeType_Auto); | ||||
|         Dqn_Str8    bytes_written_str8 = Dqn_U64ToByteSizeStr8(scratch.arena, bytes_written, Dqn_U64ByteSizeType_Auto); | ||||
|         Dqn_ErrorSink_MakeF(error, | ||||
|                             error_code, | ||||
|                             "Failed to copy file '%.*s' to '%.*s', we copied %.*s but the file size is %.*s: (%d) %s", | ||||
|                             DQN_STR_FMT(src), | ||||
|                             DQN_STR_FMT(dest), | ||||
|                             DQN_STR_FMT(bytes_written_str8), | ||||
|                             DQN_STR_FMT(file_size_str8), | ||||
|                             error_code, | ||||
|                             strerror(error_code)); | ||||
|     } | ||||
| 
 | ||||
|     if (dest_fd != -1) | ||||
|         close(dest_fd); | ||||
|     #endif | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| DQN_API bool Dqn_OS_FileMove(Dqn_Str8 src, Dqn_Str8 dest, bool overwrite) | ||||
| DQN_API bool Dqn_OS_MoveFile(Dqn_Str8 src, Dqn_Str8 dest, bool overwrite, Dqn_ErrorSink *error) | ||||
| { | ||||
|     // See: https://github.com/gingerBill/gb/blob/master/gb.h
 | ||||
|     bool result     = false; | ||||
| @ -275,11 +329,21 @@ DQN_API bool Dqn_OS_FileMove(Dqn_Str8 src, Dqn_Str8 dest, bool overwrite) | ||||
|     if (link(src.data, dest.data) == -1) { | ||||
|         // NOTE: Link can fail if we're trying to link across different volumes
 | ||||
|         // so we fall back to a binary directory.
 | ||||
|         file_moved |= Dqn_OS_FileCopy(src, dest, overwrite); | ||||
|         file_moved |= Dqn_OS_CopyFile(src, dest, overwrite, error); | ||||
|     } | ||||
| 
 | ||||
|     if (file_moved) | ||||
|         result = (unlink(src.data) != -1); // Remove original file
 | ||||
|     if (file_moved) { | ||||
|         int unlink_result = unlink(src.data); | ||||
|         if (unlink_result == -1) { | ||||
|             int error_code = errno; | ||||
|             Dqn_ErrorSink_MakeF(error, | ||||
|                                 error_code, | ||||
|                                 "File '%.*s' was moved but failed to be unlinked from old location: (%d) %s", | ||||
|                                 DQN_STR_FMT(src), | ||||
|                                 error_code, | ||||
|                                 strerror(error_code)); | ||||
|         } | ||||
|     } | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| @ -295,7 +359,7 @@ DQN_API bool Dqn_OS_DirExists(Dqn_Str8 path) | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| DQN_API bool Dqn_OS_DirMake(Dqn_Str8 path) | ||||
| DQN_API bool Dqn_OS_MakeDir(Dqn_Str8 path) | ||||
| { | ||||
|     Dqn_Scratch scratch = Dqn_Scratch_Get(nullptr); | ||||
|     bool        result  = true; | ||||
| @ -351,16 +415,8 @@ DQN_API bool Dqn_OS_DirMake(Dqn_Str8 path) | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| DQN_API bool Dqn_OS_PathDelete(Dqn_Str8 path) | ||||
| { | ||||
|     bool result = false; | ||||
|     if (Dqn_Str8_HasData(path)) | ||||
|         result = remove(path.data) == 0; | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| // NOTE: R/W Stream API ////////////////////////////////////////////////////////////////////////////
 | ||||
| DQN_API Dqn_OSFile Dqn_OS_OpenFile(Dqn_Str8 path, Dqn_OSFileOpen open_mode, uint32_t access) | ||||
| DQN_API Dqn_OSFile Dqn_OS_FileOpen(Dqn_Str8 path, Dqn_OSFileOpen open_mode, uint32_t access, Dqn_ErrorSink *error) | ||||
| { | ||||
|     Dqn_OSFile result = {}; | ||||
|     if (!Dqn_Str8_HasData(path) || path.size <= 0) | ||||
| @ -372,18 +428,15 @@ DQN_API Dqn_OSFile Dqn_OS_OpenFile(Dqn_Str8 path, Dqn_OSFileOpen open_mode, uint | ||||
|     } | ||||
| 
 | ||||
|     if (access & Dqn_OSFileAccess_Execute) { | ||||
|         result.error_size = DQN_CAST(uint16_t) Dqn_FmtBuffer3DotTruncate( | ||||
|             result.error, | ||||
|             DQN_ARRAY_UCOUNT(result.error), | ||||
|             "Open file failed: execute access not supported for \"%.*s\"", | ||||
|             DQN_STR_FMT(path)); | ||||
|         result.error = true; | ||||
|         Dqn_ErrorSink_MakeF(error, 1, "Failed to open file '%.*s': File access flag 'execute' is not supported", DQN_STR_FMT(path)); | ||||
|         DQN_INVALID_CODE_PATH; // TODO: Not supported via fopen
 | ||||
|         return result; | ||||
|     } | ||||
| 
 | ||||
|     // NOTE: fopen interface is not as expressive as the Win32
 | ||||
|     // We will fopen the file beforehand to setup the state/check for validity
 | ||||
|     // before closing and reopening it if valid with the correct request access
 | ||||
|     // before closing and reopening it with the correct request access
 | ||||
|     // permissions.
 | ||||
|     { | ||||
|         FILE *handle = nullptr; | ||||
| @ -393,13 +446,10 @@ DQN_API Dqn_OSFile Dqn_OS_OpenFile(Dqn_Str8 path, Dqn_OSFileOpen open_mode, uint | ||||
|             case Dqn_OSFileOpen_OpenAlways:   handle = fopen(path.data, "a"); break; | ||||
|             default: DQN_INVALID_CODE_PATH; break; | ||||
|         } | ||||
|         if (!handle) { | ||||
|             result.error_size = DQN_CAST(uint16_t)Dqn_FmtBuffer3DotTruncate( | ||||
|                 result.error, | ||||
|                 DQN_ARRAY_UCOUNT(result.error), | ||||
|                 "Open file failed: Could not open file in requested mode %d for \"%.*s\"", | ||||
|                 open_mode, | ||||
|                 DQN_STR_FMT(path)); | ||||
| 
 | ||||
|         if (!handle) { // TODO(doyle): FileOpen flag to string
 | ||||
|             result.error = true; | ||||
|             Dqn_ErrorSink_MakeF(error, 1, "Failed to open file '%.*s': File could not be opened in requested mode 'Dqn_OSFileOpen' flag %d", DQN_STR_FMT(path), open_mode); | ||||
|             return result; | ||||
|         } | ||||
|         fclose(handle); | ||||
| @ -416,74 +466,49 @@ DQN_API Dqn_OSFile Dqn_OS_OpenFile(Dqn_Str8 path, Dqn_OSFileOpen open_mode, uint | ||||
| 
 | ||||
|     FILE *handle = fopen(path.data, fopen_mode); | ||||
|     if (!handle) { | ||||
|         result.error_size = DQN_CAST(uint16_t) Dqn_FmtBuffer3DotTruncate( | ||||
|             result.error, | ||||
|             DQN_ARRAY_UCOUNT(result.error), | ||||
|             "Open file failed: Could not open file in fopen mode \"%s\" for \"%.*s\"", | ||||
|             fopen_mode, | ||||
|             DQN_STR_FMT(path)); | ||||
|         result.error = true; | ||||
|         Dqn_ErrorSink_MakeF(error, 1, "Failed to open file '%.*s': File could not be opened with requested access mode 'Dqn_OSFileAccess' %d", DQN_STR_FMT(path), fopen_mode); | ||||
|         return result; | ||||
|     } | ||||
|     result.handle = handle; | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| DQN_API bool Dqn_OS_WriteFileBuffer(Dqn_OSFile *file, void const *buffer, Dqn_usize size) | ||||
| DQN_API bool Dqn_OS_FileRead(Dqn_OSFile *file, void *buffer, Dqn_usize size, Dqn_ErrorSink *error) | ||||
| { | ||||
|     if (!file || !file->handle || !buffer || size <= 0 || file->error_size) | ||||
|     if (!file || !file->handle || file->error || !buffer || size <= 0) | ||||
|         return false; | ||||
| 
 | ||||
|     if (fread(buffer, size, 1, DQN_CAST(FILE *)file->handle) != 1) { | ||||
|         Dqn_Scratch scratch          = Dqn_Scratch_Get(nullptr); | ||||
|         Dqn_Str8    buffer_size_str8 = Dqn_U64ToByteSizeStr8(scratch.arena, size, Dqn_U64ByteSizeType_Auto); | ||||
|         Dqn_ErrorSink_MakeF(error, 1, "Failed to read %.*s from file", DQN_STR_FMT(buffer_size_str8)); | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     return true; | ||||
| } | ||||
| 
 | ||||
| DQN_API bool Dqn_OS_FileWritePtr(Dqn_OSFile *file, void const *buffer, Dqn_usize size, Dqn_ErrorSink *error) | ||||
| { | ||||
|     if (!file || !file->handle || file->error || !buffer || size <= 0) | ||||
|         return false; | ||||
|     bool result = fwrite(buffer, DQN_CAST(Dqn_usize)size, 1 /*count*/, DQN_CAST(FILE *)file->handle) == 1 /*count*/; | ||||
|     if (!result) { | ||||
|         Dqn_Scratch scratch          = Dqn_Scratch_Get(nullptr); | ||||
|         Dqn_Str8    buffer_size_str8 = Dqn_U64ToByteSizeStr8(scratch.arena, size, Dqn_U64ByteSizeType_Auto); | ||||
|         Dqn_ErrorSink_MakeF(error, 1, "Failed to write buffer (%s) to file handle", DQN_STR_FMT(buffer_size_str8)); | ||||
|     } | ||||
|     return result; | ||||
| } | ||||
| 
 | ||||
| DQN_API void Dqn_OS_CloseFile(Dqn_OSFile *file) | ||||
| DQN_API void Dqn_OS_FileClose(Dqn_OSFile *file) | ||||
| { | ||||
|     if (!file || !file->handle || file->error_size) | ||||
|     if (!file || !file->handle || file->error) | ||||
|         return; | ||||
|     fclose(DQN_CAST(FILE *)file->handle); | ||||
|     *file = {}; | ||||
| } | ||||
| 
 | ||||
| // NOTE: R/W Entire File ///////////////////////////////////////////////////////////////////////////
 | ||||
| DQN_API Dqn_Str8 Dqn_OS_ReadAll(Dqn_Str8 path, Dqn_Arena *arena) | ||||
| { | ||||
|     Dqn_Str8 result = {}; | ||||
|     if (!arena) | ||||
|         return result; | ||||
| 
 | ||||
|     Dqn_ArenaTempMemScope temp_mem = Dqn_ArenaTempMemScope(arena); | ||||
|     FILE *file_handle = fopen(path.data, "rb"); | ||||
|     if (!file_handle) { | ||||
|         Dqn_Log_ErrorF("Failed to open file '%.*s' using fopen", DQN_STR_FMT(path)); | ||||
|         return result; | ||||
|     } | ||||
|     DQN_DEFER { fclose(file_handle); }; | ||||
| 
 | ||||
|     fseek(file_handle, 0, SEEK_END); | ||||
|     Dqn_usize file_size = ftell(file_handle); | ||||
| 
 | ||||
|     if (DQN_CAST(long)(file_size) == -1L) { | ||||
|         Dqn_Log_ErrorF("Failed to determine '%.*s' file size using ftell", DQN_STR_FMT(path)); | ||||
|         return result; | ||||
|     } | ||||
| 
 | ||||
|     rewind(file_handle); | ||||
|     Dqn_Str8 buffer = Dqn_Str8_Alloc(arena, file_size, Dqn_ZeroMem_No); | ||||
|     if (!buffer.data) { | ||||
|         Dqn_Log_ErrorF("Failed to allocate %zu bytes to read file '%.*s'", file_size + 1, DQN_STR_FMT(path)); | ||||
|         return result; | ||||
|     } | ||||
| 
 | ||||
|     if (fread(buffer.data, file_size, 1, file_handle) != 1) { | ||||
|         Dqn_Log_ErrorF("Failed to read %zu bytes into buffer from '%.*s'", file_size, DQN_STR_FMT(path)); | ||||
|         return result; | ||||
|     } | ||||
| 
 | ||||
|     buffer.data[file_size] = 0; | ||||
|     result                 = buffer; | ||||
|     temp_mem.mem           = {}; | ||||
|     return result; | ||||
| } | ||||
| #endif // !defined(DQN_NO_OS_FILE_API)
 | ||||
| 
 | ||||
| // NOTE: [$EXEC] Dqn_OSExec ////////////////////////////////////////////////////////////////////////
 | ||||
|  | ||||
							
								
								
									
										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; | ||||
| } | ||||
| 
 | ||||
| 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 ////////////////////////////////////////////////////////////////////////
 | ||||
|  | ||||
| @ -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"); | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user