From 32222757508d44ae2de191d506909702ee7885dc Mon Sep 17 00:00:00 2001 From: Thomas Goyne Date: Thu, 20 Mar 2014 15:26:28 -0700 Subject: [PATCH] Use boost.interprocess's mmap wrapper in the PCM provider --- build/libaegisub/libaegisub.vcxproj | 3 +- build/libaegisub/libaegisub.vcxproj.filters | 9 +- libaegisub/Makefile | 1 + libaegisub/common/file_mapping.cpp | 66 +++++++++ .../libaegisub/{util_win.h => file_mapping.h} | 29 ++-- libaegisub/include/libaegisub/util.h | 2 + libaegisub/windows/access.cpp | 1 - libaegisub/windows/fs.cpp | 4 +- libaegisub/windows/path_win.cpp | 2 +- libaegisub/windows/util_win.cpp | 3 +- src/audio_provider_pcm.cpp | 130 ++++-------------- src/audio_provider_pcm.h | 37 ++--- 12 files changed, 137 insertions(+), 150 deletions(-) create mode 100644 libaegisub/common/file_mapping.cpp rename libaegisub/include/libaegisub/{util_win.h => file_mapping.h} (55%) diff --git a/build/libaegisub/libaegisub.vcxproj b/build/libaegisub/libaegisub.vcxproj index 293c84a9f..a38601917 100644 --- a/build/libaegisub/libaegisub.vcxproj +++ b/build/libaegisub/libaegisub.vcxproj @@ -52,6 +52,7 @@ + @@ -77,7 +78,6 @@ - @@ -97,6 +97,7 @@ + diff --git a/build/libaegisub/libaegisub.vcxproj.filters b/build/libaegisub/libaegisub.vcxproj.filters index ff2bba3f6..0856f727c 100644 --- a/build/libaegisub/libaegisub.vcxproj.filters +++ b/build/libaegisub/libaegisub.vcxproj.filters @@ -80,9 +80,6 @@ Header Files - - Header Files - Header Files @@ -167,6 +164,9 @@ Header Files + + Header Files + @@ -274,6 +274,9 @@ Source Files\Common + + Source Files\Common + diff --git a/libaegisub/Makefile b/libaegisub/Makefile index 245e1340b..48d2cdeb6 100644 --- a/libaegisub/Makefile +++ b/libaegisub/Makefile @@ -25,6 +25,7 @@ SRC += \ common/charset_conv.cpp \ common/color.cpp \ common/dispatch.cpp \ + common/file_mapping.cpp \ common/fs.cpp \ common/hotkey.cpp \ common/io.cpp \ diff --git a/libaegisub/common/file_mapping.cpp b/libaegisub/common/file_mapping.cpp new file mode 100644 index 000000000..d661e8e6a --- /dev/null +++ b/libaegisub/common/file_mapping.cpp @@ -0,0 +1,66 @@ +// 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 "../config.h" + +#include "libaegisub/file_mapping.h" + +#include "libaegisub/fs.h" +#include "libaegisub/util.h" + +#include +#include + +using namespace boost::interprocess; + +namespace agi { +file_mapping::file_mapping(agi::fs::path const& filename, mode_t mode) +#ifdef _WIN32 +: handle(CreateFileW(filename.wstring().c_str(), (unsigned int)mode, 0, nullptr, 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(ipcdetail::open_existing_file(filename.string().c_str(), mode)) +{ + 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); + } +} +} diff --git a/libaegisub/include/libaegisub/util_win.h b/libaegisub/include/libaegisub/file_mapping.h similarity index 55% rename from libaegisub/include/libaegisub/util_win.h rename to libaegisub/include/libaegisub/file_mapping.h index 2a90ba780..6de6d200c 100644 --- a/libaegisub/include/libaegisub/util_win.h +++ b/libaegisub/include/libaegisub/file_mapping.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010, Amar Takhar +// 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 @@ -11,18 +11,23 @@ // 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/ -/// @file util.h -/// @brief Public interface for Windows utilities. -/// @ingroup libaegisub windows +#include -#include - -#define WIN32_LEAN_AND_MEAN -#include +#include namespace agi { - namespace util { - std::string ErrorString(DWORD error); - } // namespace util -} // namespace agi + // boost::interprocess::file_mapping is awesome and uses CreateFileA on Windows + class file_mapping { + boost::interprocess::file_handle_t handle; + + public: + file_mapping(fs::path const& filename, boost::interprocess::mode_t mode); + ~file_mapping(); + boost::interprocess::mapping_handle_t get_mapping_handle() const { + return boost::interprocess::ipcdetail::mapping_handle_from_file_handle(handle); + } + }; +} diff --git a/libaegisub/include/libaegisub/util.h b/libaegisub/include/libaegisub/util.h index bca64c108..ca92c3edd 100644 --- a/libaegisub/include/libaegisub/util.h +++ b/libaegisub/include/libaegisub/util.h @@ -72,5 +72,7 @@ namespace agi { return std::any_of(std::begin(r), std::end(r), std::forward(p)); } + std::string ErrorString(int error); + } // namespace util } // namespace agi diff --git a/libaegisub/windows/access.cpp b/libaegisub/windows/access.cpp index 62181d01b..9d42b64e7 100644 --- a/libaegisub/windows/access.cpp +++ b/libaegisub/windows/access.cpp @@ -22,7 +22,6 @@ #include #include #include -#include #include #include diff --git a/libaegisub/windows/fs.cpp b/libaegisub/windows/fs.cpp index 6cff55cb6..a22d7556f 100644 --- a/libaegisub/windows/fs.cpp +++ b/libaegisub/windows/fs.cpp @@ -22,7 +22,7 @@ #include "libaegisub/charset_conv_win.h" #include "libaegisub/exception.h" #include "libaegisub/scoped_ptr.h" -#include "libaegisub/util_win.h" +#include "libaegisub/util.h" using agi::charset::ConvertW; using agi::charset::ConvertLocal; @@ -30,6 +30,8 @@ using agi::charset::ConvertLocal; #include namespace bfs = boost::filesystem; +#define WIN32_LEAN_AND_MEAN +#include #include #undef CreateDirectory diff --git a/libaegisub/windows/path_win.cpp b/libaegisub/windows/path_win.cpp index a71231d55..470d7ec30 100644 --- a/libaegisub/windows/path_win.cpp +++ b/libaegisub/windows/path_win.cpp @@ -20,7 +20,7 @@ #include -#include +#include #include diff --git a/libaegisub/windows/util_win.cpp b/libaegisub/windows/util_win.cpp index dbac2819d..3a6fa1e34 100644 --- a/libaegisub/windows/util_win.cpp +++ b/libaegisub/windows/util_win.cpp @@ -19,7 +19,6 @@ #include "../config.h" #include "libaegisub/util.h" -#include "libaegisub/util_win.h" #include "libaegisub/charset_conv_win.h" @@ -33,7 +32,7 @@ namespace agi { using agi::charset::ConvertW; -std::string ErrorString(DWORD error) { +std::string ErrorString(int error) { LPWSTR lpstr = nullptr; if(FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, nullptr, error, 0, reinterpret_cast(&lpstr), 0, nullptr) == 0) { diff --git a/src/audio_provider_pcm.cpp b/src/audio_provider_pcm.cpp index b01b18ab4..db6a3e63c 100644 --- a/src/audio_provider_pcm.cpp +++ b/src/audio_provider_pcm.cpp @@ -39,50 +39,21 @@ #include "audio_controller.h" #include "utils.h" +#include #include #include #include +#include +#include #include #include -#ifndef _WIN32 -#include -#include -#include -#endif + +using namespace boost::interprocess; PCMAudioProvider::PCMAudioProvider(agi::fs::path const& filename) -#ifdef _WIN32 -: file_handle(0, CloseHandle) -, file_mapping(0, CloseHandle) +: file(agi::util::make_unique(filename, read_only)) { - file_handle = CreateFile( - filename.c_str(), - FILE_READ_DATA, - FILE_SHARE_READ|FILE_SHARE_WRITE, - 0, - OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL|FILE_FLAG_RANDOM_ACCESS, - 0); - - if (file_handle == INVALID_HANDLE_VALUE) - throw agi::fs::FileNotFound(filename); - - file_mapping = CreateFileMapping( - file_handle, - 0, - PAGE_READONLY, - 0, 0, - 0); - - if (file_mapping == 0) - throw agi::AudioProviderOpenError("Failed creating file mapping", 0); -#else -: file_handle(open(filename.c_str(), O_RDONLY), close) -{ - if (file_handle == -1) - throw agi::fs::FileNotFound(filename.string()); -#endif float_samples = false; try { @@ -93,79 +64,36 @@ PCMAudioProvider::PCMAudioProvider(agi::fs::path const& filename) } } -PCMAudioProvider::~PCMAudioProvider() { -#ifdef _WIN32 - if (current_mapping) - UnmapViewOfFile(current_mapping); -#else - if (current_mapping) - munmap(current_mapping, mapping_length); -#endif -} +PCMAudioProvider::~PCMAudioProvider() { } -char * PCMAudioProvider::EnsureRangeAccessible(int64_t range_start, int64_t range_length) const { - if (range_start + range_length > file_size) +char *PCMAudioProvider::EnsureRangeAccessible(int64_t start, int64_t length) const { + if (start + length > file_size) throw AudioDecodeError("Attempted to map beyond end of file"); - // Check whether the requested range is already visible - if (!current_mapping || range_start < mapping_start || range_start+range_length > mapping_start+(int64_t)mapping_length) { - // It's not visible, change the current mapping - if (current_mapping) { -#ifdef _WIN32 - UnmapViewOfFile(current_mapping); -#else - munmap(current_mapping, mapping_length); -#endif - } + // Check if we can just use the current mapping + if (region && start >= mapping_start && start + length <= mapping_start + region->get_size()) + return static_cast(region->get_address()) + start - mapping_start; - // Align range start on a 1 MB boundary and go 16 MB back - mapping_start = (range_start & ~0xFFFFF) - 0x1000000; - if (mapping_start < 0) mapping_start = 0; - - if (sizeof(void*) > 4) - // Large address space, use a 2 GB mapping - mapping_length = 0x80000000; - else - // Small (32 bit) address space, use a 256 MB mapping - mapping_length = 0x10000000; - - // Make sure to always make a mapping at least as large as the requested range - if ((int64_t)mapping_length < range_length) { - if (range_length > (int64_t)(~(size_t)0)) - throw AudioDecodeError("Requested range larger than max size_t, cannot create view of file"); - mapping_length = range_length; - } - // But also make sure we don't try to make a mapping larger than the file - if (mapping_start + (int64_t)mapping_length > file_size) - mapping_length = (size_t)(file_size - mapping_start); - // We already checked that the requested range doesn't extend over the end of the file - // Hopefully this should ensure that small files are always mapped in their entirety - -#ifdef _WIN32 - LARGE_INTEGER mapping_start_li; - mapping_start_li.QuadPart = mapping_start; - current_mapping = MapViewOfFile( - file_mapping, // Mapping handle - FILE_MAP_READ, // Access type - mapping_start_li.HighPart, // Offset high-part - mapping_start_li.LowPart, // Offset low-part - mapping_length); // Length of view -#else - current_mapping = mmap(nullptr, mapping_length, PROT_READ, MAP_PRIVATE, file_handle, mapping_start); -#endif - - if (!current_mapping) - throw AudioDecodeError("Failed mapping a view of the file"); + if (sizeof(size_t) == 4) { + mapping_start = start & ~0xFFFFFULL; // Align to 1 MB bondary + length += static_cast(start - 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; } - assert(current_mapping); - assert(range_start >= mapping_start); + try { + region = agi::util::make_unique(*file, read_only, mapping_start, length); + } + catch (interprocess_exception const&) { + throw AudioDecodeError("Failed mapping a view of the file"); + } - // Difference between actual current mapping start and requested range start - ptrdiff_t rel_ofs = (ptrdiff_t)(range_start - mapping_start); - - // Calculate a pointer into current mapping for the requested range - return ((char*)current_mapping) + rel_ofs; + return static_cast(region->get_address()) + start - mapping_start; } void PCMAudioProvider::FillBuffer(void *buf, int64_t start, int64_t count) const { diff --git a/src/audio_provider_pcm.h b/src/audio_provider_pcm.h index fbd4cbf11..4b2959ac2 100644 --- a/src/audio_provider_pcm.h +++ b/src/audio_provider_pcm.h @@ -27,42 +27,23 @@ // // Aegisub Project http://www.aegisub.org/ -/// @file audio_provider_pcm.h -/// @see audio_provider_pcm.cpp -/// @ingroup audio_input -/// - -#include - -#ifdef _WIN32 -#define WIN32_LEAN_AND_MEAN -#include -#endif - #include "include/aegisub/audio_provider.h" -#include +#include +#include + +namespace agi { class file_mapping; } +namespace boost { namespace interprocess { class mapped_region; } } class PCMAudioProvider : public AudioProvider { - mutable void *current_mapping = nullptr; - -#ifdef _WIN32 + std::unique_ptr file; + mutable std::unique_ptr region; mutable int64_t mapping_start = 0; - mutable size_t mapping_length = 0; - - agi::scoped_holder file_handle; - agi::scoped_holder file_mapping; -#else - mutable off_t mapping_start = 0; - mutable size_t mapping_length = 0; - - agi::scoped_holder file_handle; -#endif protected: PCMAudioProvider(agi::fs::path const& filename); // Create base object and open the file mapping - virtual ~PCMAudioProvider(); // Closes the file mapping - char * EnsureRangeAccessible(int64_t range_start, int64_t range_length) const; // Ensure that the given range of bytes are accessible in the file mapping and return a pointer to the first byte of the requested range + ~PCMAudioProvider(); // Closes the file mapping + char *EnsureRangeAccessible(int64_t range_start, int64_t range_length) const; // Ensure that the given range of bytes are accessible in the file mapping and return a pointer to the first byte of the requested range /// Size of the opened file int64_t file_size = 0;