merlin-utils/mapped_file.cpp
Kelvin Sherlock 5159061ff0 initial WIP
2019-12-08 13:55:33 -05:00

390 lines
7.5 KiB
C++

#include "mapped_file.h"
#include "unique_resource.h"
#include <memory>
#include <functional>
#include <system_error>
namespace {
void set_or_throw_error(std::error_code *ec, int error, const std::string &what) {
if (ec) *ec = std::error_code(error, std::system_category());
else throw std::system_error(error, std::system_category(), what);
}
}
#ifdef _WIN32
#include <windows.h>
namespace {
/*
* allocating a new string could reset GetLastError() to 0.
*/
void set_or_throw_error(std::error_code *ec, const char *what) {
auto e = GetLastError();
set_or_throw_error(ec, e, what);
}
void set_or_throw_error(std::error_code *ec, const std::string &what) {
auto e = GetLastError();
set_or_throw_error(ec, e, what);
}
template<class ...Args>
HANDLE CreateFileX(const std::string &s, Args... args) {
return CreateFileA(s.c_str(), std::forward<Args>(args)...);
}
template<class ...Args>
HANDLE CreateFileX(const std::wstring &s, Args... args) {
return CreateFileW(s.c_str(), std::forward<Args>(args)...);
}
}
void mapped_file_base::close() {
if (is_open()) {
UnmapViewOfFile(_data);
CloseHandle(_map_handle);
CloseHandle(_file_handle);
reset();
}
}
template<class T>
void mapped_file_base::open_common(const T& p, mapmode flags, size_t length, size_t offset, std::error_code *ec) {
if (ec) ec->clear();
HANDLE fh;
HANDLE mh;
// length of 0 in CreateFileMapping / MapViewOfFile
// means map the entire file.
if (is_open()) close();
fh = CreateFileX(p,
flags == readonly ? GENERIC_READ : GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ,
nullptr,
OPEN_EXISTING,
flags == readonly ? FILE_ATTRIBUTE_READONLY : FILE_ATTRIBUTE_NORMAL,
nullptr
);
if (fh == INVALID_HANDLE_VALUE)
return set_or_throw_error(ec, "CreateFile");
auto fh_close = make_unique_resource(fh, CloseHandle);
if (length == -1) {
LARGE_INTEGER file_size;
GetFileSizeEx(fh, &file_size);
length = (size_t)file_size.QuadPart;
}
if (length == 0) return;
DWORD protect = 0;
DWORD access = 0;
switch (flags) {
case readonly:
protect = PAGE_READONLY;
access = FILE_MAP_READ;
break;
case readwrite:
protect = PAGE_READWRITE;
access = FILE_MAP_WRITE;
break;
case priv:
protect = PAGE_WRITECOPY;
access = FILE_MAP_COPY;
break;
}
mh = CreateFileMapping(fh, nullptr, protect, 0, 0, 0);
if (mh == INVALID_HANDLE_VALUE)
return set_or_throw_error(ec, "CreateFileMapping");
auto mh_close = make_unique_resource(mh, CloseHandle);
ULARGE_INTEGER ll;
ll.QuadPart = offset;
_data = MapViewOfFileEx(mh,
access,
ll.HighPart,
ll.LowPart,
length,
nullptr);
if (!_data)
return set_or_throw_error(ec, "MapViewOfFileEx");
_file_handle = fh_close.release();
_map_handle = mh_close.release();
_size = length;
_flags = flags;
}
void mapped_file_base::open(const std::string &p, mapmode flags, size_t length, size_t offset, std::error_code *ec) {
open_common(p, flags, length, offset, ec);
}
void mapped_file_base::open(const std::wstring &p, mapmode flags, size_t length, size_t offset, std::error_code *ec) {
open_common(p, flags, length, offset, ec);
}
template<class T>
void mapped_file_base::create_common(const T& p, size_t length, std::error_code *ec) {
if (ec) ec->clear();
const size_t offset = 0;
HANDLE fh;
HANDLE mh;
LARGE_INTEGER file_size;
const DWORD protect = PAGE_READWRITE;
const DWORD access = FILE_MAP_WRITE;
if (is_open()) close();
fh = CreateFileX(p,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ,
nullptr,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
nullptr
);
if (fh == INVALID_HANDLE_VALUE)
return set_or_throw_error(ec, "CreateFile");
auto fh_close = make_unique_resource(fh, CloseHandle);
if (length == 0) return;
file_size.QuadPart = length;
if (!SetFilePointerEx(fh, file_size, nullptr, FILE_BEGIN))
return set_or_throw_error(ec, "SetFilePointerEx");
if (!SetEndOfFile(fh))
return set_or_throw_error(ec, "SetEndOfFile");
mh = CreateFileMapping(fh, nullptr, protect, 0, 0, 0);
if (mh == INVALID_HANDLE_VALUE)
return set_or_throw_error(ec, "CreateFileMapping");
auto mh_close = make_unique_resource(mh, CloseHandle);
ULARGE_INTEGER ll;
ll.QuadPart = offset;
_data = MapViewOfFileEx(mh,
access,
ll.HighPart,
ll.LowPart,
length,
nullptr);
if (!_data)
return set_or_throw_error(ec, "MapViewOfFileEx");
_file_handle = fh_close.release();
_map_handle = mh_close.release();
_size = length;
_flags = readwrite;
}
void mapped_file_base::create(const std::string &p, size_t length, std::error_code *ec) {
create_common(p, length, ec);
}
void mapped_file_base::create(const std::wstring &p, size_t length, std::error_code *ec) {
create_common(p, length, ec);
}
#else
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <cerrno>
namespace {
void set_or_throw_error(std::error_code *ec, const char *what) {
set_or_throw_error(ec, errno, what);
}
void set_or_throw_error(std::error_code *ec, const std::string &what) {
set_or_throw_error(ec, errno, what);
}
}
void mapped_file_base::close() {
if (is_open()) {
::munmap(_data, _size);
::close(_fd);
reset();
}
}
void mapped_file_base::open(const std::string& p, mapmode flags, size_t length, size_t offset, std::error_code *ec) {
if (ec) ec->clear();
int fd;
int oflags = 0;
if (is_open()) close();
switch (flags) {
case readonly:
oflags = O_RDONLY;
break;
default:
oflags = O_RDWR;
break;
}
fd = ::open(p.c_str(), oflags);
if (fd < 0) {
return set_or_throw_error(ec, "open");
}
auto close_fd = make_unique_resource(fd, ::close);
if (length == -1) {
struct stat st;
if (::fstat(fd, &st) < 0) {
set_or_throw_error(ec, "stat");
return;
}
length = st.st_size;
}
if (length == 0) return;
_data = ::mmap(0, length,
flags == readonly ? PROT_READ : PROT_READ | PROT_WRITE,
flags == priv ? MAP_PRIVATE : MAP_SHARED,
fd, offset);
if (_data == MAP_FAILED) {
_data = nullptr;
return set_or_throw_error(ec, "mmap");
}
_fd = close_fd.release();
_size = length;
_flags = flags;
}
void mapped_file_base::create(const std::string& p, size_t length, std::error_code *ec) {
if (ec) ec->clear();
int fd;
const size_t offset = 0;
if (is_open()) close();
fd = ::open(p.c_str(), O_RDWR | O_CREAT | O_TRUNC, 0666);
if (fd < 0) {
return set_or_throw_error(ec, "open");
}
auto close_fd = make_unique_resource(fd, ::close);
if (length == 0) return;
if (::ftruncate(fd, length) < 0) {
return set_or_throw_error(ec, "ftruncate");
}
_data = ::mmap(0, length,
PROT_READ | PROT_WRITE,
MAP_SHARED,
fd, offset);
if (_data == MAP_FAILED) {
_data = nullptr;
return set_or_throw_error(ec, "mmap");
}
_fd = close_fd.release();
_size = length;
_flags = readwrite;
}
#endif
void mapped_file_base::reset() {
_data = nullptr;
_size = 0;
_flags = readonly;
#ifdef _WIN32
_file_handle = nullptr;
_map_handle = nullptr;
#else
_fd = -1;
#endif
}
void mapped_file_base::swap(mapped_file_base &rhs)
{
if (std::addressof(rhs) != this) {
std::swap(_data, rhs._data);
std::swap(_size, rhs._size);
std::swap(_flags, rhs._flags);
#ifdef _WIN32
std::swap(_file_handle, rhs._file_handle);
std::swap(_map_handle, rhs._map_handle);
#else
std::swap(_fd, rhs._fd);
#endif
}
}
mapped_file::mapped_file(mapped_file &&rhs) : mapped_file() {
swap(rhs);
//rhs.reset();
}
mapped_file& mapped_file::operator=(mapped_file &&rhs) {
if (std::addressof(rhs) == this) return *this;
swap(rhs);
rhs.close();
//rhs.reset();
return *this;
}