libs/iostreams/src/mapped_file.cpp
// (C) Copyright Craig Henderson 2002 'boost/memmap.hpp' from sandbox
// (C) Copyright Jonathan Turkanis 2004.
// (C) Copyright Jonathan Graehl 2004.
// (C) Copyright Jorge Lodos 2008.
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt.)
// Define BOOST_IOSTREAMS_SOURCE so that <boost/iostreams/detail/config.hpp>
// knows that we are building the library (possibly exporting code), rather
// than using it (possibly importing code).
#define BOOST_IOSTREAMS_SOURCE
#include <cassert>
#include <stdexcept>
#include <boost/iostreams/detail/config/rtl.hpp>
#include <boost/iostreams/detail/config/windows_posix.hpp>
#include <boost/iostreams/detail/file_handle.hpp>
#include <boost/iostreams/detail/system_failure.hpp>
#include <boost/iostreams/device/mapped_file.hpp>
#include <boost/throw_exception.hpp>
#include <boost/numeric/conversion/cast.hpp>
#ifdef BOOST_IOSTREAMS_WINDOWS
# define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers
# include <windows.h>
#else
# include <errno.h>
# include <fcntl.h>
# include <sys/mman.h> // mmap, munmap.
# include <sys/stat.h>
# include <sys/types.h> // struct stat.
# include <unistd.h> // sysconf.
#endif
namespace boost { namespace iostreams {
namespace detail {
// Class containing the platform-sepecific implementation
// Invariant: The members params_, data_, size_, handle_ (and mapped_handle_
// on Windows) either
// - all have default values (or INVALID_HANDLE_VALUE for
// Windows handles), or
// - all have values reflecting a successful mapping.
// In the first case, error_ may be true, reflecting a recent unsuccessful
// open or close attempt; in the second case, error_ is always false.
class mapped_file_impl {
public:
typedef mapped_file_source::size_type size_type;
typedef mapped_file_source::param_type param_type;
typedef mapped_file_source::mapmode mapmode;
BOOST_STATIC_CONSTANT(
size_type, max_length = mapped_file_source::max_length);
mapped_file_impl();
~mapped_file_impl();
void open(param_type p);
bool is_open() const { return data_ != 0; }
void close();
bool error() const { return error_; }
mapmode flags() const { return params_.flags; }
std::size_t size() const { return static_cast<std::size_t>(size_); }
char* data() const { return data_; }
void resize(stream_offset new_size);
static int alignment();
private:
void open_file(param_type p);
void try_map_file(param_type p);
void map_file(param_type& p);
bool unmap_file();
void clear(bool error);
void cleanup_and_throw(const char* msg);
param_type params_;
char* data_;
stream_offset size_;
file_handle handle_;
#ifdef BOOST_IOSTREAMS_WINDOWS
file_handle mapped_handle_;
#endif
bool error_;
};
mapped_file_impl::mapped_file_impl() { clear(false); }
mapped_file_impl::~mapped_file_impl()
{ try { close(); } catch (...) { } }
void mapped_file_impl::open(param_type p)
{
if (is_open())
boost::throw_exception(BOOST_IOSTREAMS_FAILURE("file already open"));
p.normalize();
open_file(p);
map_file(p); // May modify p.hint
params_ = p;
}
void mapped_file_impl::close()
{
if (data_ == 0)
return;
bool error = false;
error = !unmap_file() || error;
error =
#ifdef BOOST_IOSTREAMS_WINDOWS
!::CloseHandle(handle_)
#else
::close(handle_) != 0
#endif
|| error;
clear(error);
if (error)
throw_system_failure("failed closing mapped file");
}
void mapped_file_impl::resize(stream_offset new_size)
{
if (!is_open())
boost::throw_exception(BOOST_IOSTREAMS_FAILURE("file is closed"));
if (flags() & mapped_file::priv)
boost::throw_exception(
BOOST_IOSTREAMS_FAILURE("can't resize private mapped file")
);
if (!(flags() & mapped_file::readwrite))
boost::throw_exception(
BOOST_IOSTREAMS_FAILURE("can't resize readonly mapped file")
);
if (params_.offset >= new_size)
boost::throw_exception(
BOOST_IOSTREAMS_FAILURE("can't resize below mapped offset")
);
if (!unmap_file())
cleanup_and_throw("failed unmapping file");
#ifdef BOOST_IOSTREAMS_WINDOWS
stream_offset offset = ::SetFilePointer(handle_, 0, NULL, FILE_CURRENT);
if (offset == INVALID_SET_FILE_POINTER && ::GetLastError() != NO_ERROR)
cleanup_and_throw("failed querying file pointer");
LONG sizehigh = (new_size >> (sizeof(LONG) * 8));
LONG sizelow = (new_size & 0xffffffff);
DWORD result = ::SetFilePointer(handle_, sizelow, &sizehigh, FILE_BEGIN);
if ((result == INVALID_SET_FILE_POINTER && ::GetLastError() != NO_ERROR)
|| !::SetEndOfFile(handle_))
cleanup_and_throw("failed resizing mapped file");
sizehigh = (offset >> (sizeof(LONG) * 8));
sizelow = (offset & 0xffffffff);
::SetFilePointer(handle_, sizelow, &sizehigh, FILE_BEGIN);
#else
if (BOOST_IOSTREAMS_FD_TRUNCATE(handle_, new_size) == -1)
cleanup_and_throw("failed resizing mapped file");
#endif
size_ = new_size;
param_type p(params_);
map_file(p); // May modify p.hint
params_ = p;
}
int mapped_file_impl::alignment()
{
#ifdef BOOST_IOSTREAMS_WINDOWS
SYSTEM_INFO info;
::GetSystemInfo(&info);
return static_cast<int>(info.dwAllocationGranularity);
#else
return static_cast<int>(sysconf(_SC_PAGESIZE));
#endif
}
void mapped_file_impl::open_file(param_type p)
{
bool readonly = p.flags != mapped_file::readwrite;
#ifdef BOOST_IOSTREAMS_WINDOWS
// Open file
DWORD dwDesiredAccess =
readonly ?
GENERIC_READ :
(GENERIC_READ | GENERIC_WRITE);
DWORD dwCreationDisposition = (p.new_file_size != 0 && !readonly) ?
CREATE_ALWAYS :
OPEN_EXISTING;
DWORD dwFlagsandAttributes =
readonly ?
FILE_ATTRIBUTE_READONLY :
FILE_ATTRIBUTE_TEMPORARY;
handle_ = p.path.is_wide() ?
::CreateFileW(
p.path.c_wstr(),
dwDesiredAccess,
FILE_SHARE_READ,
NULL,
dwCreationDisposition,
dwFlagsandAttributes,
NULL ) :
::CreateFileA(
p.path.c_str(),
dwDesiredAccess,
FILE_SHARE_READ,
NULL,
dwCreationDisposition,
dwFlagsandAttributes,
NULL );
if (handle_ == INVALID_HANDLE_VALUE)
cleanup_and_throw("failed opening file");
// Set file size
if (p.new_file_size != 0 && !readonly) {
LONG sizehigh = (p.new_file_size >> (sizeof(LONG) * 8));
LONG sizelow = (p.new_file_size & 0xffffffff);
DWORD result = ::SetFilePointer(handle_, sizelow, &sizehigh, FILE_BEGIN);
if ((result == INVALID_SET_FILE_POINTER && ::GetLastError() != NO_ERROR)
|| !::SetEndOfFile(handle_))
cleanup_and_throw("failed setting file size");
}
// Determine file size. Dynamically locate GetFileSizeEx for compatibility
// with old Platform SDK (thanks to Pavel Vozenilik).
typedef BOOL (WINAPI *func)(HANDLE, PLARGE_INTEGER);
HMODULE hmod = ::GetModuleHandleA("kernel32.dll");
func get_size =
reinterpret_cast<func>(::GetProcAddress(hmod, "GetFileSizeEx"));
if (get_size) {
LARGE_INTEGER info;
if (get_size(handle_, &info)) {
boost::intmax_t size =
( (static_cast<boost::intmax_t>(info.HighPart) << 32) |
info.LowPart );
size_ =
static_cast<std::size_t>(
p.length != max_length ?
std::min<boost::intmax_t>(p.length, size) :
size
);
} else {
cleanup_and_throw("failed querying file size");
return;
}
} else {
DWORD hi;
DWORD low;
if ( (low = ::GetFileSize(handle_, &hi))
!=
INVALID_FILE_SIZE )
{
boost::intmax_t size =
(static_cast<boost::intmax_t>(hi) << 32) | low;
size_ =
static_cast<std::size_t>(
p.length != max_length ?
std::min<boost::intmax_t>(p.length, size) :
size
);
} else {
cleanup_and_throw("failed querying file size");
return;
}
}
#else // #ifdef BOOST_IOSTREAMS_WINDOWS
// Open file
int flags = (readonly ? O_RDONLY : O_RDWR);
if (p.new_file_size != 0 && !readonly)
flags |= (O_CREAT | O_TRUNC);
#ifdef _LARGEFILE64_SOURCE
flags |= O_LARGEFILE;
#endif
errno = 0;
if (p.path.is_wide()) { errno = EINVAL; cleanup_and_throw("wide path not supported here"); } // happens on CYGWIN
handle_ = ::open(p.path.c_str(), flags, S_IRWXU);
if (errno != 0)
cleanup_and_throw("failed opening file");
//--------------Set file size---------------------------------------------//
if (p.new_file_size != 0 && !readonly)
if (BOOST_IOSTREAMS_FD_TRUNCATE(handle_, p.new_file_size) == -1)
cleanup_and_throw("failed setting file size");
//--------------Determine file size---------------------------------------//
bool success = true;
if (p.length != max_length) {
size_ = p.length;
} else {
struct BOOST_IOSTREAMS_FD_STAT info;
success = ::BOOST_IOSTREAMS_FD_FSTAT(handle_, &info) != -1;
size_ = info.st_size;
}
if (!success)
cleanup_and_throw("failed querying file size");
#endif // #ifdef BOOST_IOSTREAMS_WINDOWS
}
void mapped_file_impl::try_map_file(param_type p)
{
bool priv = p.flags == mapped_file::priv;
bool readonly = p.flags == mapped_file::readonly;
#ifdef BOOST_IOSTREAMS_WINDOWS
// Create mapping
DWORD protect = priv ?
PAGE_WRITECOPY :
readonly ?
PAGE_READONLY :
PAGE_READWRITE;
mapped_handle_ =
::CreateFileMappingA(
handle_,
NULL,
protect,
0,
0,
NULL );
if (mapped_handle_ == NULL)
cleanup_and_throw("failed create mapping");
// Access data
DWORD access = priv ?
FILE_MAP_COPY :
readonly ?
FILE_MAP_READ :
FILE_MAP_WRITE;
void* data =
::MapViewOfFileEx(
mapped_handle_,
access,
(DWORD) (p.offset >> 32),
(DWORD) (p.offset & 0xffffffff),
(SIZE_T) (numeric_cast<size_type>(size_) != max_length ? size_ : 0),
(LPVOID) p.hint );
if (!data)
cleanup_and_throw("failed mapping view");
#else
void* data =
::BOOST_IOSTREAMS_FD_MMAP(
const_cast<char*>(p.hint),
size_,
readonly ? PROT_READ : (PROT_READ | PROT_WRITE),
priv ? MAP_PRIVATE : MAP_SHARED,
handle_,
p.offset );
if (data == MAP_FAILED)
cleanup_and_throw("failed mapping file");
#endif
data_ = static_cast<char*>(data);
}
void mapped_file_impl::map_file(param_type& p)
{
try {
try_map_file(p);
} catch (const std::exception&) {
if (p.hint) {
p.hint = 0;
try_map_file(p);
} else {
throw;
}
}
}
bool mapped_file_impl::unmap_file()
{
#ifdef BOOST_IOSTREAMS_WINDOWS
bool error = false;
error = !::UnmapViewOfFile(data_) || error;
error = !::CloseHandle(mapped_handle_) || error;
mapped_handle_ = NULL;
return !error;
#else
return ::munmap(data_, size_) == 0;
#endif
}
void mapped_file_impl::clear(bool error)
{
params_ = param_type();
data_ = 0;
size_ = 0;
#ifdef BOOST_IOSTREAMS_WINDOWS
handle_ = INVALID_HANDLE_VALUE;
mapped_handle_ = NULL;
#else
handle_ = 0;
#endif
error_ = error;
}
// Called when an error is encountered during the execution of open_file or
// map_file
void mapped_file_impl::cleanup_and_throw(const char* msg)
{
#ifdef BOOST_IOSTREAMS_WINDOWS
DWORD error = GetLastError();
if (mapped_handle_ != NULL)
::CloseHandle(mapped_handle_);
if (handle_ != INVALID_HANDLE_VALUE)
::CloseHandle(handle_);
SetLastError(error);
#else
int error = errno;
if (handle_ != 0)
::close(handle_);
errno = error;
#endif
clear(true);
boost::iostreams::detail::throw_system_failure(msg);
}
//------------------Implementation of mapped_file_params_base-----------------//
void mapped_file_params_base::normalize()
{
if (mode && flags)
boost::throw_exception(BOOST_IOSTREAMS_FAILURE(
"at most one of 'mode' and 'flags' may be specified"
));
if (flags) {
switch (flags) {
case mapped_file::readonly:
case mapped_file::readwrite:
case mapped_file::priv:
break;
default:
boost::throw_exception(BOOST_IOSTREAMS_FAILURE("invalid flags"));
}
} else {
flags = (mode & BOOST_IOS::out) ?
mapped_file::readwrite :
mapped_file::readonly;
mode = BOOST_IOS::openmode();
}
if (offset < 0)
boost::throw_exception(BOOST_IOSTREAMS_FAILURE("invalid offset"));
if (new_file_size < 0)
boost::throw_exception(
BOOST_IOSTREAMS_FAILURE("invalid new file size")
);
}
} // End namespace detail.
//------------------Implementation of mapped_file_source----------------------//
mapped_file_source::mapped_file_source()
: pimpl_(new impl_type)
{ }
mapped_file_source::mapped_file_source(const mapped_file_source& other)
: pimpl_(other.pimpl_)
{ }
bool mapped_file_source::is_open() const
{ return pimpl_->is_open(); }
void mapped_file_source::close() { pimpl_->close(); }
// safe_bool is explicitly qualified below to please msvc 7.1
mapped_file_source::operator mapped_file_source::safe_bool() const
{ return pimpl_->error() ? &safe_bool_helper::x : 0; }
bool mapped_file_source::operator!() const
{ return pimpl_->error(); }
mapped_file_source::mapmode mapped_file_source::flags() const
{ return pimpl_->flags(); }
mapped_file_source::size_type mapped_file_source::size() const
{ return pimpl_->size(); }
const char* mapped_file_source::data() const { return pimpl_->data(); }
const char* mapped_file_source::begin() const { return data(); }
const char* mapped_file_source::end() const { return data() + size(); }
int mapped_file_source::alignment()
{ return detail::mapped_file_impl::alignment(); }
void mapped_file_source::init() { pimpl_.reset(new impl_type); }
void mapped_file_source::open_impl(const param_type& p)
{ pimpl_->open(p); }
//------------------Implementation of mapped_file-----------------------------//
mapped_file::mapped_file(const mapped_file& other)
: delegate_(other.delegate_)
{ }
void mapped_file::resize(stream_offset new_size)
{ delegate_.pimpl_->resize(new_size); }
//------------------Implementation of mapped_file_sink------------------------//
mapped_file_sink::mapped_file_sink(const mapped_file_sink& other)
: mapped_file(static_cast<const mapped_file&>(other))
{ }
//----------------------------------------------------------------------------//
} } // End namespaces iostreams, boost.