// Copyright (c) 2014, Thomas Goyne // // Permission to use, copy, modify, and distribute this software for any // purpose with or without fee is hereby granted, provided that the above // copyright notice and this permission notice appear in all copies. // // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. // // Aegisub Project http://www.aegisub.org/ #include "libaegisub/file_mapping.h" #include "libaegisub/fs.h" #include "libaegisub/make_unique.h" #include "libaegisub/util.h" #include #include #include #ifdef _WIN32 #include #endif using namespace boost::interprocess; namespace { char *map(int64_t s_offset, uint64_t length, boost::interprocess::mode_t mode, uint64_t file_size, agi::file_mapping const& file, std::unique_ptr& region, uint64_t& mapping_start) { static char dummy = 0; if (length == 0) return &dummy; auto offset = static_cast(s_offset); if (offset + length > file_size) throw agi::InternalError("Attempted to map beyond end of file"); // Check if we can just use the current mapping if (region && offset >= mapping_start && offset + length <= mapping_start + region->get_size()) return static_cast(region->get_address()) + offset - mapping_start; if (sizeof(size_t) == 4) { mapping_start = offset & ~0xFFFFFULL; // Align to 1 MB bondary length += static_cast(offset - mapping_start); // Map 16 MB or length rounded up to the next MB length = std::min(std::max(0x1000000U, (length + 0xFFFFF) & ~0xFFFFF), file_size - mapping_start); } else { // Just map the whole file mapping_start = 0; length = file_size; } if (length > std::numeric_limits::max()) throw std::bad_alloc(); try { region = agi::make_unique(file, mode, mapping_start, static_cast(length)); } catch (interprocess_exception const&) { throw agi::fs::FileSystemUnknownError("Failed mapping a view of the file"); } return static_cast(region->get_address()) + offset - mapping_start; } } namespace agi { file_mapping::file_mapping(fs::path const& filename, bool temporary) #ifdef _WIN32 : handle(CreateFileW(filename.wstring().c_str(), temporary ? read_write : read_only, temporary ? FILE_SHARE_READ | FILE_SHARE_WRITE : FILE_SHARE_READ, nullptr, temporary ? OPEN_ALWAYS : OPEN_EXISTING, 0, 0)) { if (handle == ipcdetail::invalid_file()) { switch (GetLastError()) { case ERROR_FILE_NOT_FOUND: case ERROR_PATH_NOT_FOUND: throw fs::FileNotFound(filename); case ERROR_ACCESS_DENIED: throw fs::ReadDenied(filename); default: throw fs::FileSystemUnknownError(util::ErrorString(GetLastError())); } } #else : handle(temporary ? ipcdetail::create_or_open_file(filename.string().c_str(), read_write) : ipcdetail::open_existing_file(filename.string().c_str(), read_only)) { if (handle == ipcdetail::invalid_file()) { switch (errno) { case ENOENT: throw fs::FileNotFound(filename); case EACCES: throw fs::ReadDenied(filename); case EIO: throw fs::FileSystemUnknownError("Fatal I/O opening path: " + filename.string()); } } #endif } file_mapping::~file_mapping() { if (handle != ipcdetail::invalid_file()) { ipcdetail::close_file(handle); } } read_file_mapping::read_file_mapping(fs::path const& filename) : file(filename, false) { offset_t size = 0; ipcdetail::get_file_size(file.get_mapping_handle().handle, size); file_size = static_cast(size); } read_file_mapping::~read_file_mapping() { } const char *read_file_mapping::read() { return read(0, size()); } const char *read_file_mapping::read(int64_t offset, uint64_t length) { return map(offset, length, read_only, file_size, file, region, mapping_start); } temp_file_mapping::temp_file_mapping(fs::path const& filename, uint64_t size) : file(filename, true) , file_size(size) { auto handle = file.get_mapping_handle().handle; #ifdef _WIN32 LARGE_INTEGER li; li.QuadPart = size; SetFilePointerEx(handle, li, nullptr, FILE_BEGIN); SetEndOfFile(handle); #else unlink(filename.string().c_str()); if (ftruncate(handle, size) == -1) { switch (errno) { case EBADF: throw InternalError("Error opening file " + filename.string() + " not handled"); case EFBIG: throw fs::DriveFull(filename); case EINVAL: throw InternalError("File opened incorrectly: " + filename.string()); case EROFS: throw fs::WriteDenied(filename); default: throw fs::FileSystemUnknownError("Unknown error opening file: " + filename.string()); } } #endif } temp_file_mapping::~temp_file_mapping() { } const char *temp_file_mapping::read(int64_t offset, uint64_t length) { return map(offset, length, read_only, file_size, file, read_region, read_mapping_start); } char *temp_file_mapping::write(int64_t offset, uint64_t length) { return map(offset, length, read_write, file_size, file, write_region, write_mapping_start); } }