/** * Jingga * * @copyright Jingga * @license OMS License 2.0 * @version 1.0.0 * @link https://jingga.app */ #ifndef TOS_PLATFORM_WIN32_FILE_UTILS_C #define TOS_PLATFORM_WIN32_FILE_UTILS_C #include #include #include #ifdef _MSC_VER #include #endif #include "../../stdlib/Types.h" #include "../../utils/Utils.h" #include "../../utils/TestUtils.h" #include "../../memory/RingMemory.h" #include "../../log/Log.h" typedef HANDLE FileHandle; typedef HANDLE MMFHandle; typedef OVERLAPPED file_overlapped; struct FileBodyAsync { // doesn't include null termination (same as strlen) uint64 size; byte* content; OVERLAPPED ov; }; inline MMFHandle file_mmf_handle(FileHandle fp) { return CreateFileMappingA(fp, NULL, PAGE_READONLY, 0, 0, NULL); } inline void* mmf_region_init(MMFHandle fh, size_t offset, size_t length = 0) { DWORD high = (DWORD) ((offset >> 32) & 0xFFFFFFFF); DWORD low = (DWORD) (offset & 0xFFFFFFFF); return MapViewOfFile(fh, FILE_MAP_READ, high, low, length); } inline void mmf_region_release(void* fh) { UnmapViewOfFile(fh); } inline void file_mmf_close(MMFHandle fh) { CloseHandle(fh); } inline void relative_to_absolute(const char* rel, char* path) { char self_path[MAX_PATH]; int32 self_path_length = GetModuleFileNameA(NULL, self_path, MAX_PATH); if (self_path_length == 0) { return; } const char* temp = rel; if (temp[0] == '.' && temp[1] == '/') { temp += 2; } char* last = self_path + self_path_length; while (*last != '\\' && self_path_length > 0) { --last; --self_path_length; } ++self_path_length; memcpy(path, self_path, self_path_length); str_copy_short(path + self_path_length, temp); } inline uint64 file_size(const char* path) { // @performance Profile against fseek strategy FileHandle fp; if (*path == '.') { char full_path[MAX_PATH]; relative_to_absolute(path, full_path); fp = CreateFileA((LPCSTR) full_path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); } else { fp = CreateFileA((LPCSTR) path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); } if (fp == INVALID_HANDLE_VALUE) { return 0; } LARGE_INTEGER size; if (!GetFileSizeEx(fp, &size)) { CloseHandle(fp); } CloseHandle(fp); return size.QuadPart; } inline bool file_exists(const char* path) { DWORD file_attr; if (*path == '.') { char full_path[MAX_PATH]; relative_to_absolute(path, full_path); file_attr = GetFileAttributesA(full_path); } else { file_attr = GetFileAttributesA(path); } return file_attr != INVALID_FILE_ATTRIBUTES; } inline void file_read(const char* path, FileBody* file, RingMemory* ring = NULL) { FileHandle fp; if (*path == '.') { char full_path[MAX_PATH]; relative_to_absolute(path, full_path); fp = CreateFileA((LPCSTR) full_path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); } else { fp = CreateFileA((LPCSTR) path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); } if (fp == INVALID_HANDLE_VALUE) { file->size = 0; return; } if (file->size == 0) { LARGE_INTEGER size; if (!GetFileSizeEx(fp, &size)) { CloseHandle(fp); file->content = NULL; return; } file->size = size.QuadPart + 1; } if (ring != NULL) { file->content = ring_get_memory(ring, file->size); } DWORD bytes_read; if (!ReadFile(fp, file->content, (uint32) file->size - 1, &bytes_read, NULL)) { CloseHandle(fp); file->content = NULL; return; } CloseHandle(fp); file->content[bytes_read] = '\0'; file->size = bytes_read + 1; LOG_INCREMENT_BY(DEBUG_COUNTER_DRIVE_READ, bytes_read); } // @question Do we really need length? we have file.size we could use as we do in a function above inline void file_read(const char* path, FileBody* file, uint64 offset, uint64 length = MAX_UINT64, RingMemory* ring = NULL) { FileHandle fp; if (*path == '.') { char full_path[MAX_PATH]; relative_to_absolute(path, full_path); fp = CreateFileA((LPCSTR) full_path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); } else { fp = CreateFileA((LPCSTR) path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); } if (fp == INVALID_HANDLE_VALUE) { file->size = 0; return; } LARGE_INTEGER size; if (!GetFileSizeEx(fp, &size)) { CloseHandle(fp); file->content = NULL; return; } // Ensure the offset and length do not exceed the file size uint64 file_size = size.QuadPart; if (offset >= file_size) { file->size = 0; file->content = NULL; CloseHandle(fp); return; } // Adjust the length to read so that it does not exceed the file size uint64 read_length = OMS_MIN(length, file_size - offset); if (ring != NULL) { file->content = ring_get_memory(ring, read_length + 1); } // Move the file pointer to the offset position LARGE_INTEGER li; li.QuadPart = offset; if (SetFilePointerEx(fp, li, NULL, FILE_BEGIN) == 0) { CloseHandle(fp); file->content = NULL; return; } DWORD bytes_read; if (!ReadFile(fp, file->content, (uint32) read_length, &bytes_read, NULL)) { CloseHandle(fp); file->content = NULL; return; } CloseHandle(fp); file->content[bytes_read] = '\0'; file->size = bytes_read; LOG_INCREMENT_BY(DEBUG_COUNTER_DRIVE_READ, bytes_read); } inline void file_read(FileHandle fp, FileBody* file, uint64 offset = 0, uint64 length = MAX_UINT64, RingMemory* ring = NULL) { LARGE_INTEGER size; if (!GetFileSizeEx(fp, &size)) { file->content = NULL; ASSERT_SIMPLE(false); return; } // Ensure the offset and length do not exceed the file size uint64 file_size = size.QuadPart; if (offset >= file_size) { file->size = 0; file->content = NULL; ASSERT_SIMPLE(false); return; } // Adjust the length to read so that it does not exceed the file size uint64 read_length = OMS_MIN(length, file_size - offset); if (ring != NULL) { file->content = ring_get_memory(ring, read_length + 1); } // Move the file pointer to the offset position LARGE_INTEGER li; li.QuadPart = offset; if (SetFilePointerEx(fp, li, NULL, FILE_BEGIN) == 0) { file->content = NULL; ASSERT_SIMPLE(false); return; } DWORD bytes_read; if (!ReadFile(fp, file->content, (uint32) read_length, &bytes_read, NULL)) { file->content = NULL; ASSERT_SIMPLE(false); return; } file->content[bytes_read] = '\0'; file->size = bytes_read; LOG_INCREMENT_BY(DEBUG_COUNTER_DRIVE_READ, bytes_read); } inline bool file_read_line( FileHandle fp, char* line_buffer, size_t buffer_size, char internal_buffer[512], ssize_t* internal_buffer_size, char** internal_pos ) { if (!(*internal_pos)) { *internal_pos = internal_buffer; } size_t line_filled = 0; while (line_filled < buffer_size - 1) { // Refill the internal buffer if empty if (*internal_pos == internal_buffer + *internal_buffer_size) { if (!ReadFile(fp, internal_buffer, 512, (DWORD *) internal_buffer_size, NULL) || *internal_buffer_size == 0 ) { line_buffer[line_filled] = '\0'; return line_filled > 0; } *internal_pos = internal_buffer; } char current_char = **internal_pos; ++(*internal_pos); // Handle line endings (\n, \r, \r\n, \n\r) if (current_char == '\n' || current_char == '\r') { if ((*internal_pos < internal_buffer + *internal_buffer_size) && (**internal_pos == '\n' || **internal_pos == '\r') && **internal_pos != current_char ) { ++(*internal_pos); } line_buffer[line_filled] = '\0'; // Successfully read a line return true; } line_buffer[line_filled++] = current_char; } line_buffer[line_filled] = '\0'; return true; } inline uint64 file_read_struct(const char* path, void* file, uint32 size) { FileHandle fp; if (*path == '.') { char full_path[MAX_PATH]; relative_to_absolute(path, full_path); fp = CreateFileA((LPCSTR) full_path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); } else { fp = CreateFileA((LPCSTR) path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); } if (fp == INVALID_HANDLE_VALUE) { return 0; } LARGE_INTEGER fsize; if (!GetFileSizeEx(fp, &fsize)) { CloseHandle(fp); return 0; } DWORD read; ASSERT_SIMPLE(fsize.QuadPart > size); if (!ReadFile(fp, file, (uint32) size, &read, NULL)) { CloseHandle(fp); return 0; } CloseHandle(fp); LOG_INCREMENT_BY(DEBUG_COUNTER_DRIVE_READ, read); return read; } inline bool file_write(const char* path, const FileBody* file) { FileHandle fp; if (*path == '.') { char full_path[MAX_PATH]; relative_to_absolute(path, full_path); fp = CreateFileA((LPCSTR) full_path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ); } else { fp = CreateFileA((LPCSTR) path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ); } if (fp == INVALID_HANDLE_VALUE) { return false; } DWORD written; DWORD length = (DWORD) file->size; if (!WriteFile(fp, file->content, length, &written, NULL)) { CloseHandle(fp); return false; } CloseHandle(fp); LOG_INCREMENT_BY(DEBUG_COUNTER_DRIVE_WRITE, length); return true; } inline bool file_write_struct(const char* path, const void* file, uint32 size) { FileHandle fp; if (*path == '.') { char full_path[MAX_PATH]; relative_to_absolute(path, full_path); fp = CreateFileA((LPCSTR) full_path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ); } else { fp = CreateFileA((LPCSTR) path, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ); } DWORD written; ASSERT_SIMPLE(size < MAX_UINT32); if (!WriteFile(fp, file, size, &written, NULL)) { CloseHandle(fp); return false; } CloseHandle(fp); LOG_INCREMENT_BY(DEBUG_COUNTER_DRIVE_WRITE, written); return true; } inline void file_copy(const char* src, const char* dst) { if (*src == '.') { char src_full_path[MAX_PATH]; relative_to_absolute(src, src_full_path); if (*dst == '.') { char dst_full_path[MAX_PATH]; relative_to_absolute(dst, dst_full_path); CopyFileA((LPCSTR) src_full_path, (LPCSTR) dst_full_path, false); } else { CopyFileA((LPCSTR) src_full_path, (LPCSTR) dst, false); } } else if (*dst == '.') { char dst_full_path[MAX_PATH]; relative_to_absolute(dst, dst_full_path); CopyFileA((LPCSTR) src, (LPCSTR) dst_full_path, false); } else { CopyFileA((LPCSTR) src, (LPCSTR) dst, false); } } inline void file_close_handle(FileHandle fp) { CloseHandle(fp); } inline HANDLE file_append_handle(const char* path) { FileHandle fp; if (*path == '.') { char full_path[MAX_PATH]; relative_to_absolute(path, full_path); fp = CreateFileA((LPCSTR) full_path, FILE_APPEND_DATA, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ); } else { fp = CreateFileA((LPCSTR) path, FILE_APPEND_DATA, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL ); } if (fp == INVALID_HANDLE_VALUE) { return NULL; } return fp; } inline bool file_read_async( FileHandle fp, FileBodyAsync* file, uint64_t offset = 0, uint64_t length = MAX_UINT64, RingMemory* ring = NULL ) { LARGE_INTEGER size; if (!GetFileSizeEx(fp, &size)) { file->content = NULL; ASSERT_SIMPLE(false); return false; } // Ensure the offset and length do not exceed the file size uint64_t file_size = size.QuadPart; if (offset >= file_size) { file->size = 0; file->content = NULL; ASSERT_SIMPLE(false); return false; } // Adjust the length to read so that it does not exceed the file size uint64 read_length = OMS_MIN(length, file_size - offset); // Allocate memory for the content if (ring != NULL) { file->content = ring_get_memory(ring, read_length); } if (!file->content) { ASSERT_SIMPLE(false); return false; } file->ov.Offset = (DWORD)(offset & 0xFFFFFFFF); file->ov.OffsetHigh = (DWORD)(offset >> 32); // Auto-reset event file->ov.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); DWORD bytes_read = 0; if (!ReadFile(fp, file->content, (DWORD) read_length, &bytes_read, &file->ov)) { DWORD error = GetLastError(); if (error != ERROR_IO_PENDING) { free(file->content); file->content = NULL; ASSERT_SIMPLE(false); return false; } } file->size = read_length; LOG_INCREMENT_BY(DEBUG_COUNTER_DRIVE_READ, read_length); return true; } inline void file_async_wait(FileHandle fp, file_overlapped* overlapped, bool wait) { DWORD bytesTransferred; GetOverlappedResult(fp, overlapped, &bytesTransferred, wait); } inline FileHandle file_read_handle(const char* path) { FileHandle fp; if (*path == '.') { char full_path[MAX_PATH]; relative_to_absolute(path, full_path); fp = CreateFileA((LPCSTR) full_path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); } else { fp = CreateFileA((LPCSTR) path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); } if (fp == INVALID_HANDLE_VALUE) { return NULL; } return fp; } inline FileHandle file_read_async_handle(const char* path) { FileHandle fp; if (*path == '.') { char full_path[MAX_PATH]; relative_to_absolute(path, full_path); fp = CreateFileA((LPCSTR) full_path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL ); } else { fp = CreateFileA((LPCSTR) path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL ); } if (fp == INVALID_HANDLE_VALUE) { return NULL; } return fp; } bool file_append(const char* path, const char* file) { FileHandle fp; if (*path == '.') { char full_path[MAX_PATH]; relative_to_absolute(path, full_path); fp = CreateFileA((LPCSTR) full_path, FILE_APPEND_DATA, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); } else { fp = CreateFileA((LPCSTR) path, FILE_APPEND_DATA, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); } if (fp == INVALID_HANDLE_VALUE) { return false; } DWORD written; DWORD length = (DWORD) strlen(file); if (!WriteFile(fp, file, length, &written, NULL)) { CloseHandle(fp); return false; } CloseHandle(fp); LOG_INCREMENT_BY(DEBUG_COUNTER_DRIVE_WRITE, written); return true; } inline bool file_append(FileHandle fp, const char* file) { if (fp == INVALID_HANDLE_VALUE) { ASSERT_SIMPLE(false); return false; } DWORD written; DWORD length = (DWORD) strlen(file); if (!WriteFile(fp, file, length, &written, NULL)) { ASSERT_SIMPLE(false); return false; } LOG_INCREMENT_BY(DEBUG_COUNTER_DRIVE_WRITE, written); return true; } inline bool file_append(FileHandle fp, const char* file, size_t length) { if (fp == INVALID_HANDLE_VALUE) { ASSERT_SIMPLE(false); return false; } DWORD written; if (!WriteFile(fp, file, (uint32) length, &written, NULL)) { ASSERT_SIMPLE(false); return false; } LOG_INCREMENT_BY(DEBUG_COUNTER_DRIVE_WRITE, written); return true; } inline bool file_append(const char* path, const FileBody* file) { FileHandle fp; if (*path == '.') { char full_path[MAX_PATH]; relative_to_absolute(path, full_path); fp = CreateFileA((LPCSTR) full_path, FILE_APPEND_DATA, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); } else { fp = CreateFileA((LPCSTR) path, FILE_APPEND_DATA, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); } if (fp == INVALID_HANDLE_VALUE) { return false; } DWORD bytes; DWORD length = (DWORD) file->size; if (!WriteFile(fp, file->content, length, &bytes, NULL)) { CloseHandle(fp); return false; } CloseHandle(fp); LOG_INCREMENT_BY(DEBUG_COUNTER_DRIVE_WRITE, bytes); return true; } inline uint64 file_last_modified(const char* path) { WIN32_FIND_DATA find_data; FileHandle fp; if (*path == '.') { char full_path[MAX_PATH]; relative_to_absolute(path, full_path); fp = FindFirstFileA(full_path, (LPWIN32_FIND_DATAA) &find_data); } else { fp = FindFirstFileA(path, (LPWIN32_FIND_DATAA) &find_data); } FILETIME modified = {}; if(fp != INVALID_HANDLE_VALUE) { modified = find_data.ftLastWriteTime; FindClose(fp); } ULARGE_INTEGER ull; ull.LowPart = modified.dwLowDateTime; ull.HighPart = modified.dwHighDateTime; return ull.QuadPart; } inline void self_path(char* path) { GetModuleFileNameA(NULL, (LPSTR) path, MAX_PATH); } #endif