diff --git a/XenonUtils/memory_mapped_file.cpp b/XenonUtils/memory_mapped_file.cpp index ba3c5d8..530321d 100644 --- a/XenonUtils/memory_mapped_file.cpp +++ b/XenonUtils/memory_mapped_file.cpp @@ -1,5 +1,7 @@ #include "memory_mapped_file.h" +#include + #if !defined(_WIN32) # include # include @@ -34,14 +36,19 @@ MemoryMappedFile::MemoryMappedFile(MemoryMappedFile &&other) other.fileMappingHandle = nullptr; other.fileView = nullptr; other.fileSize.QuadPart = 0; + other.streamingMode = false; #else fileHandle = other.fileHandle; fileView = other.fileView; fileSize = other.fileSize; + mappedLength = other.mappedLength; + streamingMode = other.streamingMode; other.fileHandle = -1; other.fileView = MAP_FAILED; other.fileSize = 0; + other.mappedLength = 0; + other.streamingMode = false; #endif } @@ -103,14 +110,23 @@ bool MemoryMappedFile::open(const std::filesystem::path &path) } fileView = mmap(nullptr, fileSize, PROT_READ, MAP_PRIVATE, fileHandle, 0); - if (fileView == MAP_FAILED) + if (fileView != MAP_FAILED) { - fprintf(stderr, "mmap failed with error %s.\n", strerror(errno)); - ::close(fileHandle); - fileHandle = -1; - return false; + mappedLength = fileSize; + return true; } + static constexpr size_t PartialMapSize = 128 * 1024 * 1024; + const size_t partialMapSize = std::min(static_cast(fileSize), PartialMapSize); + fileView = mmap(nullptr, partialMapSize, PROT_READ, MAP_PRIVATE, fileHandle, 0); + if (fileView != MAP_FAILED) + { + mappedLength = static_cast(partialMapSize); + return true; + } + + fprintf(stderr, "mmap failed with error %s, falling back to streaming I/O.\n", strerror(errno)); + streamingMode = true; return true; #endif } @@ -135,7 +151,7 @@ void MemoryMappedFile::close() #else if (fileView != MAP_FAILED) { - munmap(fileView, fileSize); + munmap(fileView, mappedLength); } if (fileHandle != -1) @@ -148,14 +164,86 @@ void MemoryMappedFile::close() bool MemoryMappedFile::isOpen() const { #if defined(_WIN32) - return (fileView != nullptr); + return (fileView != nullptr) || (streamingMode && fileHandle != nullptr); +#else + return (fileView != MAP_FAILED) || (streamingMode && fileHandle != -1); +#endif +} + +bool MemoryMappedFile::readAt(size_t offset, void *buffer, size_t size) const +{ + if (!isOpen() || buffer == nullptr) + return false; + + if (static_cast(offset) < 0 || static_cast(offset + size) > fileSize) + return false; + +#if defined(_WIN32) + if (fileView != nullptr) + { + memcpy(buffer, reinterpret_cast(fileView) + offset, size); + return true; + } + + if (!streamingMode || fileHandle == nullptr) + return false; + + OVERLAPPED overlapped = {}; + overlapped.Offset = static_cast(offset & 0xFFFFFFFF); + overlapped.OffsetHigh = static_cast(offset >> 32); + DWORD bytesRead = 0; + if (!ReadFile(fileHandle, buffer, static_cast(size), &bytesRead, &overlapped) || bytesRead != size) + return false; + + return true; #else - return (fileView != MAP_FAILED); + if (fileView != MAP_FAILED) + { + const size_t mappedEnd = static_cast(mappedLength); + if (offset + size <= mappedEnd) + { + memcpy(buffer, reinterpret_cast(fileView) + offset, size); + return true; + } + + if (offset >= mappedEnd) + { + return pread(fileHandle, buffer, size, static_cast(offset)) == static_cast(size); + } + + const size_t mappedBytes = mappedEnd - offset; + memcpy(buffer, reinterpret_cast(fileView) + offset, mappedBytes); + const size_t remainingBytes = size - mappedBytes; + return pread(fileHandle, static_cast(buffer) + mappedBytes, remainingBytes, static_cast(mappedEnd)) == static_cast(remainingBytes); + } + + if (!streamingMode || fileHandle == -1) + return false; + + size_t bytesRead = 0; + while (bytesRead < size) + { + const ssize_t result = pread(fileHandle, static_cast(buffer) + bytesRead, size - bytesRead, static_cast(offset + bytesRead)); + if (result <= 0) + return false; + + bytesRead += static_cast(result); + } + + return true; #endif } uint8_t *MemoryMappedFile::data() const { +#if defined(_WIN32) + if (fileView == nullptr) + return nullptr; +#else + if (fileView == MAP_FAILED) + return nullptr; +#endif + return reinterpret_cast(fileView); } diff --git a/XenonUtils/memory_mapped_file.h b/XenonUtils/memory_mapped_file.h index a3de88b..640cc42 100644 --- a/XenonUtils/memory_mapped_file.h +++ b/XenonUtils/memory_mapped_file.h @@ -14,10 +14,13 @@ struct MemoryMappedFile { HANDLE fileMappingHandle = nullptr; LPVOID fileView = nullptr; LARGE_INTEGER fileSize = {}; + bool streamingMode = false; #else int fileHandle = -1; void *fileView = MAP_FAILED; off_t fileSize = 0; + off_t mappedLength = 0; + bool streamingMode = false; #endif MemoryMappedFile(); @@ -27,6 +30,7 @@ struct MemoryMappedFile { bool open(const std::filesystem::path &path); void close(); bool isOpen() const; + bool readAt(size_t offset, void *buffer, size_t size) const; uint8_t *data() const; size_t size() const; };