536 lines
14 KiB
C++
536 lines
14 KiB
C++
// IACore-OSS; The Core Library for All IA Open Source Projects
|
|
// Copyright (C) 2026 IAS (ias@iasoft.dev)
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
#include <IACore/FileOps.hpp>
|
|
#include <cerrno>
|
|
#include <cstdio>
|
|
|
|
#if IA_PLATFORM_UNIX
|
|
#include <fcntl.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
#endif
|
|
|
|
namespace IACore {
|
|
|
|
HashMap<const u8 *, std::tuple<void *, void *, void *>> FileOps::s_mapped_files;
|
|
|
|
auto FileOps::unmap_file(const u8 *mapped_ptr) -> void {
|
|
if (!s_mapped_files.contains(mapped_ptr)) {
|
|
return;
|
|
}
|
|
|
|
auto it = s_mapped_files.find(mapped_ptr);
|
|
const auto handles = it->second;
|
|
s_mapped_files.erase(it);
|
|
|
|
#if IA_PLATFORM_WINDOWS
|
|
::UnmapViewOfFile(std::get<1>(handles));
|
|
::CloseHandle(static_cast<HANDLE>(std::get<2>(handles)));
|
|
|
|
const auto handle = static_cast<HANDLE>(std::get<0>(handles));
|
|
if (handle != INVALID_HANDLE_VALUE) {
|
|
::CloseHandle(handle);
|
|
}
|
|
#elif IA_PLATFORM_UNIX
|
|
::munmap(std::get<1>(handles), (usize)std::get<2>(handles));
|
|
const auto fd = (i32)((u64)std::get<0>(handles));
|
|
if (fd != -1) {
|
|
::close(fd);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
auto FileOps::map_shared_memory(const String &name, usize size, bool is_owner)
|
|
-> Result<u8 *> {
|
|
#if IA_PLATFORM_WINDOWS
|
|
const int wchars_num =
|
|
MultiByteToWideChar(CP_UTF8, 0, name.c_str(), -1, NULL, 0);
|
|
std::wstring w_name(wchars_num, 0);
|
|
MultiByteToWideChar(CP_UTF8, 0, name.c_str(), -1, &w_name[0], wchars_num);
|
|
|
|
HANDLE h_map = NULL;
|
|
if (is_owner) {
|
|
h_map = CreateFileMappingW(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE,
|
|
(DWORD)(size >> 32), (DWORD)(size & 0xFFFFFFFF),
|
|
w_name.c_str());
|
|
} else {
|
|
h_map = OpenFileMappingW(FILE_MAP_ALL_ACCESS, false, w_name.c_str());
|
|
}
|
|
|
|
if (h_map == NULL) {
|
|
return fail("Failed to {} shared memory '{}'",
|
|
is_owner ? "owner" : "consumer", name);
|
|
}
|
|
|
|
auto *result =
|
|
static_cast<u8 *>(MapViewOfFile(h_map, FILE_MAP_ALL_ACCESS, 0, 0, size));
|
|
if (result == NULL) {
|
|
CloseHandle(h_map);
|
|
return fail("Failed to map view of shared memory '{}'", name);
|
|
}
|
|
|
|
s_mapped_files[result] = std::make_tuple((void *)INVALID_HANDLE_VALUE,
|
|
(void *)result, (void *)h_map);
|
|
return result;
|
|
|
|
#elif IA_PLATFORM_UNIX
|
|
int fd = -1;
|
|
if (is_owner) {
|
|
fd = shm_open(name.c_str(), O_RDWR | O_CREAT | O_TRUNC, 0666);
|
|
if (fd != -1) {
|
|
if (ftruncate(fd, size) == -1) {
|
|
close(fd);
|
|
shm_unlink(name.c_str());
|
|
return fail("Failed to truncate shared memory '{}'", name);
|
|
}
|
|
}
|
|
} else {
|
|
fd = shm_open(name.c_str(), O_RDWR, 0666);
|
|
}
|
|
|
|
if (fd == -1) {
|
|
return fail("Failed to {} shared memory '{}'",
|
|
is_owner ? "owner" : "consumer", name);
|
|
}
|
|
|
|
void *addr = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
|
|
if (addr == MAP_FAILED) {
|
|
close(fd);
|
|
return fail("Failed to mmap shared memory '{}'", name);
|
|
}
|
|
|
|
auto *result = static_cast<u8 *>(addr);
|
|
|
|
s_mapped_files[result] =
|
|
std::make_tuple((void *)((u64)fd), (void *)addr, (void *)size);
|
|
return result;
|
|
#endif
|
|
}
|
|
|
|
auto FileOps::unlink_shared_memory(const String &name) -> void {
|
|
if (name.empty()) {
|
|
return;
|
|
}
|
|
#if IA_PLATFORM_UNIX
|
|
shm_unlink(name.c_str());
|
|
#endif
|
|
}
|
|
|
|
auto FileOps::map_file(const Path &path, usize &size) -> Result<const u8 *> {
|
|
#if IA_PLATFORM_WINDOWS
|
|
const auto handle = CreateFileA(
|
|
path.string().c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
|
|
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL);
|
|
|
|
if (handle == INVALID_HANDLE_VALUE) {
|
|
return fail("Failed to open {} for memory mapping", path.string());
|
|
}
|
|
|
|
LARGE_INTEGER file_size;
|
|
if (!GetFileSizeEx(handle, &file_size)) {
|
|
CloseHandle(handle);
|
|
return fail("Failed to get size of {} for memory mapping", path.string());
|
|
}
|
|
size = static_cast<usize>(file_size.QuadPart);
|
|
if (size == 0) {
|
|
CloseHandle(handle);
|
|
return fail("Failed to get size of {} for memory mapping", path.string());
|
|
}
|
|
|
|
auto h_map = CreateFileMappingW(handle, NULL, PAGE_READONLY, 0, 0, NULL);
|
|
if (h_map == NULL) {
|
|
CloseHandle(handle);
|
|
return fail("Failed to memory map {}", path.string());
|
|
}
|
|
|
|
const auto *result =
|
|
static_cast<const u8 *>(MapViewOfFile(h_map, FILE_MAP_READ, 0, 0, 0));
|
|
if (result == NULL) {
|
|
CloseHandle(handle);
|
|
CloseHandle(h_map);
|
|
return fail("Failed to memory map {}", path.string());
|
|
}
|
|
s_mapped_files[result] = std::make_tuple(
|
|
(void *)handle, (void *)const_cast<u8 *>(result), (void *)h_map);
|
|
return result;
|
|
|
|
#elif IA_PLATFORM_UNIX
|
|
const auto handle = open(path.string().c_str(), O_RDONLY);
|
|
if (handle == -1) {
|
|
return fail("Failed to open {} for memory mapping", path.string());
|
|
}
|
|
struct stat sb;
|
|
if (fstat(handle, &sb) == -1) {
|
|
close(handle);
|
|
return fail("Failed to get stats of {} for memory mapping", path.string());
|
|
}
|
|
size = static_cast<usize>(sb.st_size);
|
|
if (size == 0) {
|
|
close(handle);
|
|
return fail("Failed to get size of {} for memory mapping", path.string());
|
|
}
|
|
void *addr = mmap(nullptr, size, PROT_READ, MAP_PRIVATE, handle, 0);
|
|
if (addr == MAP_FAILED) {
|
|
close(handle);
|
|
return fail("Failed to memory map {}", path.string());
|
|
}
|
|
const auto *result = static_cast<const u8 *>(addr);
|
|
madvise(addr, size, MADV_SEQUENTIAL);
|
|
s_mapped_files[result] =
|
|
std::make_tuple((void *)((u64)handle), (void *)addr, (void *)size);
|
|
return result;
|
|
#endif
|
|
}
|
|
|
|
auto FileOps::stream_to_file(const Path &path, bool overwrite)
|
|
-> Result<StreamWriter> {
|
|
if (!overwrite && std::filesystem::exists(path)) {
|
|
return fail("File already exists: {}", path.string());
|
|
}
|
|
return StreamWriter::create_from_file(path);
|
|
}
|
|
|
|
auto FileOps::stream_from_file(const Path &path) -> Result<StreamReader> {
|
|
if (!std::filesystem::exists(path)) {
|
|
return fail("File does not exist: {}", path.string());
|
|
}
|
|
return StreamReader::create_from_file(path);
|
|
}
|
|
|
|
auto FileOps::read_text_file(const Path &path) -> Result<String> {
|
|
auto *f = fopen(path.string().c_str(), "r");
|
|
if (!f) {
|
|
return fail("Failed to open file: {}", path.string());
|
|
}
|
|
String result;
|
|
fseek(f, 0, SEEK_END);
|
|
const long len = ftell(f);
|
|
if (len > 0) {
|
|
result.resize(static_cast<usize>(len));
|
|
fseek(f, 0, SEEK_SET);
|
|
fread(result.data(), 1, result.size(), f);
|
|
}
|
|
fclose(f);
|
|
return result;
|
|
}
|
|
|
|
auto FileOps::read_binary_file(const Path &path) -> Result<Vec<u8>> {
|
|
auto *f = fopen(path.string().c_str(), "rb");
|
|
if (!f) {
|
|
return fail("Failed to open file: {}", path.string());
|
|
}
|
|
Vec<u8> result;
|
|
fseek(f, 0, SEEK_END);
|
|
const long len = ftell(f);
|
|
if (len > 0) {
|
|
result.resize(static_cast<usize>(len));
|
|
fseek(f, 0, SEEK_SET);
|
|
fread(result.data(), 1, result.size(), f);
|
|
}
|
|
fclose(f);
|
|
return result;
|
|
}
|
|
|
|
auto FileOps::write_text_file(const Path &path, const String &contents,
|
|
bool overwrite) -> Result<usize> {
|
|
const char *mode = overwrite ? "w" : "wx";
|
|
auto *f = fopen(path.string().c_str(), mode);
|
|
if (!f) {
|
|
if (!overwrite && errno == EEXIST) {
|
|
return fail("File already exists: {}", path.string());
|
|
}
|
|
return fail("Failed to write to file: {}", path.string());
|
|
}
|
|
const auto result = fwrite(contents.data(), 1, contents.size(), f);
|
|
fclose(f);
|
|
return result;
|
|
}
|
|
|
|
auto FileOps::write_binary_file(const Path &path, Span<const u8> contents,
|
|
bool overwrite) -> Result<usize> {
|
|
const char *mode = overwrite ? "w" : "wx";
|
|
auto *f = fopen(path.string().c_str(), mode);
|
|
if (!f) {
|
|
if (!overwrite && errno == EEXIST) {
|
|
return fail("File already exists: {}", path.string());
|
|
}
|
|
return fail("Failed to write to file: {}", path.string());
|
|
}
|
|
const auto result = fwrite(contents.data(), 1, contents.size(), f);
|
|
fclose(f);
|
|
return result;
|
|
}
|
|
|
|
auto FileOps::normalize_executable_path(const Path &path) -> Path {
|
|
Path result = path;
|
|
|
|
#if IA_PLATFORM_WINDOWS
|
|
if (!result.has_extension()) {
|
|
result.replace_extension(".exe");
|
|
}
|
|
#elif IA_PLATFORM_UNIX
|
|
if (result.extension() == ".exe") {
|
|
result.replace_extension("");
|
|
}
|
|
|
|
if (result.is_relative()) {
|
|
String path_str = result.string();
|
|
if (!path_str.starts_with("./") && !path_str.starts_with("../")) {
|
|
result = "./" + path_str;
|
|
}
|
|
}
|
|
#endif
|
|
return result;
|
|
}
|
|
|
|
auto FileOps::native_open_file(const Path &path, FileAccess access,
|
|
FileMode mode, u32 permissions)
|
|
-> Result<NativeFileHandle> {
|
|
#if IA_PLATFORM_WINDOWS
|
|
DWORD dw_access = 0;
|
|
DWORD dw_share = FILE_SHARE_READ;
|
|
DWORD dw_disposition = 0;
|
|
DWORD dw_flags_and_attributes = FILE_ATTRIBUTE_NORMAL;
|
|
|
|
switch (access) {
|
|
case FileAccess::Read:
|
|
dw_access = GENERIC_READ;
|
|
break;
|
|
case FileAccess::Write:
|
|
dw_access = GENERIC_WRITE;
|
|
break;
|
|
case FileAccess::ReadWrite:
|
|
dw_access = GENERIC_READ | GENERIC_WRITE;
|
|
break;
|
|
}
|
|
|
|
switch (mode) {
|
|
case FileMode::OpenExisting:
|
|
dw_disposition = OPEN_EXISTING;
|
|
break;
|
|
case FileMode::OpenAlways:
|
|
dw_disposition = OPEN_ALWAYS;
|
|
break;
|
|
case FileMode::CreateNew:
|
|
dw_disposition = CREATE_NEW;
|
|
break;
|
|
case FileMode::CreateAlways:
|
|
dw_disposition = CREATE_ALWAYS;
|
|
break;
|
|
case FileMode::TruncateExisting:
|
|
dw_disposition = TRUNCATE_EXISTING;
|
|
break;
|
|
}
|
|
|
|
HANDLE h_file = CreateFileA(path.string().c_str(), dw_access, dw_share, NULL,
|
|
dw_disposition, dw_flags_and_attributes, NULL);
|
|
|
|
if (h_file == INVALID_HANDLE_VALUE) {
|
|
return fail("Failed to open file '{}': {}", path.string(), GetLastError());
|
|
}
|
|
|
|
return h_file;
|
|
|
|
#elif IA_PLATFORM_UNIX
|
|
int flags = 0;
|
|
|
|
switch (access) {
|
|
case FileAccess::Read:
|
|
flags = O_RDONLY;
|
|
break;
|
|
case FileAccess::Write:
|
|
flags = O_WRONLY;
|
|
break;
|
|
case FileAccess::ReadWrite:
|
|
flags = O_RDWR;
|
|
break;
|
|
}
|
|
|
|
switch (mode) {
|
|
case FileMode::OpenExisting:
|
|
break;
|
|
case FileMode::OpenAlways:
|
|
flags |= O_CREAT;
|
|
break;
|
|
case FileMode::CreateNew:
|
|
flags |= O_CREAT | O_EXCL;
|
|
break;
|
|
case FileMode::CreateAlways:
|
|
flags |= O_CREAT | O_TRUNC;
|
|
break;
|
|
case FileMode::TruncateExisting:
|
|
flags |= O_TRUNC;
|
|
break;
|
|
}
|
|
|
|
int fd = open(path.string().c_str(), flags, permissions);
|
|
|
|
if (fd == -1) {
|
|
return fail("Failed to open file '{}': {}", path.string(), errno);
|
|
}
|
|
|
|
return fd;
|
|
#endif
|
|
}
|
|
|
|
auto FileOps::native_close_file(NativeFileHandle handle) -> void {
|
|
if (handle == INVALID_FILE_HANDLE) {
|
|
return;
|
|
}
|
|
|
|
#if IA_PLATFORM_WINDOWS
|
|
CloseHandle(handle);
|
|
#elif IA_PLATFORM_UNIX
|
|
close(handle);
|
|
#endif
|
|
}
|
|
|
|
// =============================================================================
|
|
// MemoryMappedRegion
|
|
// =============================================================================
|
|
|
|
FileOps::MemoryMappedRegion::~MemoryMappedRegion() { unmap(); }
|
|
|
|
FileOps::MemoryMappedRegion::MemoryMappedRegion(
|
|
MemoryMappedRegion &&other) noexcept {
|
|
*this = std::move(other);
|
|
}
|
|
|
|
auto FileOps::MemoryMappedRegion::operator=(MemoryMappedRegion &&other) noexcept
|
|
-> MemoryMappedRegion & {
|
|
if (this != &other) {
|
|
unmap();
|
|
m_ptr = other.m_ptr;
|
|
m_size = other.m_size;
|
|
#if IA_PLATFORM_WINDOWS
|
|
m_map_handle = other.m_map_handle;
|
|
other.m_map_handle = NULL;
|
|
#endif
|
|
other.m_ptr = nullptr;
|
|
other.m_size = 0;
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
auto FileOps::MemoryMappedRegion::map(NativeFileHandle handle, u64 offset,
|
|
usize size) -> Result<void> {
|
|
unmap();
|
|
|
|
if (handle == INVALID_FILE_HANDLE) {
|
|
return fail("Invalid file handle provided to Map");
|
|
}
|
|
|
|
if (size == 0) {
|
|
return fail("Cannot map region of size 0");
|
|
}
|
|
|
|
#if IA_PLATFORM_WINDOWS
|
|
LARGE_INTEGER file_size;
|
|
if (!GetFileSizeEx(handle, &file_size)) {
|
|
return fail("Failed to get file size");
|
|
}
|
|
|
|
u64 end_offset = offset + size;
|
|
if (static_cast<u64>(file_size.QuadPart) < end_offset) {
|
|
LARGE_INTEGER new_size;
|
|
new_size.QuadPart = static_cast<LONGLONG>(end_offset);
|
|
if (!SetFilePointerEx(handle, new_size, NULL, FILE_BEGIN)) {
|
|
return fail("Failed to seek to new end of file");
|
|
}
|
|
|
|
if (!SetEndOfFile(handle)) {
|
|
return fail("Failed to extend file for mapping");
|
|
}
|
|
}
|
|
|
|
m_map_handle = CreateFileMappingW(handle, NULL, PAGE_READWRITE, 0, 0, NULL);
|
|
if (m_map_handle == NULL) {
|
|
return fail("CreateFileMapping failed: {}", GetLastError());
|
|
}
|
|
|
|
DWORD offset_high = static_cast<DWORD>(offset >> 32);
|
|
DWORD offset_low = static_cast<DWORD>(offset & 0xFFFFFFFF);
|
|
|
|
m_ptr = static_cast<u8 *>(MapViewOfFile(m_map_handle, FILE_MAP_WRITE,
|
|
offset_high, offset_low, size));
|
|
if (m_ptr == NULL) {
|
|
CloseHandle(m_map_handle);
|
|
m_map_handle = NULL;
|
|
return fail("MapViewOfFile failed (Offset: {}, Size: {}): {}", offset, size,
|
|
GetLastError());
|
|
}
|
|
m_size = size;
|
|
|
|
#elif IA_PLATFORM_UNIX
|
|
struct stat sb;
|
|
if (fstat(handle, &sb) == -1) {
|
|
return fail("Failed to fstat file");
|
|
}
|
|
|
|
u64 end_offset = offset + size;
|
|
if (static_cast<u64>(sb.st_size) < end_offset) {
|
|
if (ftruncate(handle, static_cast<off_t>(end_offset)) == -1) {
|
|
return fail("Failed to ftruncate (extend) file");
|
|
}
|
|
}
|
|
|
|
void *ptr = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, handle,
|
|
static_cast<off_t>(offset));
|
|
if (ptr == MAP_FAILED) {
|
|
return fail("mmap failed: {}", errno);
|
|
}
|
|
|
|
m_ptr = static_cast<u8 *>(ptr);
|
|
m_size = size;
|
|
|
|
madvise(m_ptr, m_size, MADV_SEQUENTIAL);
|
|
#endif
|
|
|
|
return {};
|
|
}
|
|
|
|
auto FileOps::MemoryMappedRegion::unmap() -> void {
|
|
if (!m_ptr) {
|
|
return;
|
|
}
|
|
|
|
#if IA_PLATFORM_WINDOWS
|
|
UnmapViewOfFile(m_ptr);
|
|
if (m_map_handle) {
|
|
CloseHandle(m_map_handle);
|
|
m_map_handle = NULL;
|
|
}
|
|
#elif IA_PLATFORM_UNIX
|
|
munmap(m_ptr, m_size);
|
|
#endif
|
|
m_ptr = nullptr;
|
|
m_size = 0;
|
|
}
|
|
|
|
auto FileOps::MemoryMappedRegion::flush() -> void {
|
|
if (!m_ptr) {
|
|
return;
|
|
}
|
|
|
|
#if IA_PLATFORM_WINDOWS
|
|
FlushViewOfFile(m_ptr, m_size);
|
|
#elif IA_PLATFORM_UNIX
|
|
msync(m_ptr, m_size, MS_SYNC);
|
|
#endif
|
|
}
|
|
|
|
} // namespace IACore
|