Support NativeFileStream via Boost.Iostreams
So it can also be enabled when not using libstdc++.
This commit is contained in:
parent
f60f79d6f3
commit
749eea2ab6
|
@ -143,10 +143,35 @@ set(META_VERSION_PATCH 1)
|
||||||
include(3rdParty)
|
include(3rdParty)
|
||||||
use_iconv(AUTO_LINKAGE REQUIRED)
|
use_iconv(AUTO_LINKAGE REQUIRED)
|
||||||
|
|
||||||
# configure use of native file buffer
|
# configure use of native file buffer and its backend implementation if enabled
|
||||||
option(USE_NATIVE_FILE_BUFFER "enables use of native file buffer, affects ABI" OFF)
|
option(USE_NATIVE_FILE_BUFFER "enables use of native file buffer, affects ABI" OFF)
|
||||||
|
option(FORCE_BOOST_IOSTREAMS_FOR_NATIVE_FILE_BUFFER "forces use of Boost.Iostreams for native file buffer" OFF)
|
||||||
if(USE_NATIVE_FILE_BUFFER)
|
if(USE_NATIVE_FILE_BUFFER)
|
||||||
list(APPEND META_PUBLIC_COMPILE_DEFINITIONS ${META_PROJECT_VARNAME}_USE_NATIVE_FILE_BUFFER)
|
list(APPEND META_PUBLIC_COMPILE_DEFINITIONS ${META_PROJECT_VARNAME}_USE_NATIVE_FILE_BUFFER)
|
||||||
|
|
||||||
|
# check whether __gnu_cxx::stdio_filebuf is available
|
||||||
|
try_compile(GNU_CXX_STDIO_FILEBUF_AVAILABLE ${CMAKE_CURRENT_BINARY_DIR}
|
||||||
|
SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/feature_detection/stdio_filebuf.cpp
|
||||||
|
OUTPUT_VARIABLE GNU_CXX_STDIO_FILEBUF_CHECK_LOG
|
||||||
|
)
|
||||||
|
|
||||||
|
# use __gnu_cxx::stdio_filebuf if available or fallback to boost::iostreams::stream_buffer
|
||||||
|
if(GNU_CXX_STDIO_FILEBUF_AVAILABLE AND NOT FORCE_BOOST_IOSTREAMS_FOR_NATIVE_FILE_BUFFER)
|
||||||
|
message(STATUS "Using __gnu_cxx::stdio_filebuf for NativeFileStream")
|
||||||
|
set_source_files_properties(
|
||||||
|
io/nativefilestream.cpp
|
||||||
|
PROPERTIES COMPILE_DEFINITIONS ${META_PROJECT_VARNAME}_USE_GNU_CXX_STDIO_FILEBUF
|
||||||
|
)
|
||||||
|
else()
|
||||||
|
message(STATUS "Using boost::iostreams::stream_buffer<boost::iostreams::file_descriptor_sink> for NativeFileStream")
|
||||||
|
use_external_library(boost_iostreams AUTO_LINKAGE REQUIRED)
|
||||||
|
set_source_files_properties(
|
||||||
|
io/nativefilestream.cpp
|
||||||
|
PROPERTIES COMPILE_DEFINITIONS ${META_PROJECT_VARNAME}_USE_BOOST_IOSTREAMS
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
else()
|
||||||
|
message(STATUS "Using std::fstream for NativeFileStream")
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# configure forcing UTF-8 code page under Windows
|
# configure forcing UTF-8 code page under Windows
|
||||||
|
|
|
@ -236,7 +236,7 @@ macro(find_external_library_from_package NAME PKGNAME VERSION INCLUDE_VAR LIBRAR
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# use the find_library approach first because it is less buggy when trying to detect static libraries
|
# use the find_library approach first because it is less buggy when trying to detect static libraries
|
||||||
# caveat: this way include dirs are not detected - however those are mostly the the default anyways and
|
# caveat: this way include dirs are not detected - however those are mostly the default anyways and
|
||||||
# can also be set manually by the user in case the auto-detection is not sufficient
|
# can also be set manually by the user in case the auto-detection is not sufficient
|
||||||
find_external_library("${NAME}" "${LINKAGE}" OPTIONAL)
|
find_external_library("${NAME}" "${LINKAGE}" OPTIONAL)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
// assume __gnu_cxx::stdio_filebuf is available if this program compiles
|
||||||
|
|
||||||
|
#include <ext/stdio_filebuf.h>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
const int fd = 0;
|
||||||
|
__gnu_cxx::stdio_filebuf<char> buf(fd, std::ios_base::in);
|
||||||
|
return buf.fd();
|
||||||
|
}
|
|
@ -1,7 +1,7 @@
|
||||||
#include "./misc.h"
|
#include "./misc.h"
|
||||||
#include "./catchiofailure.h"
|
#include "./catchiofailure.h"
|
||||||
|
#include "./nativefilestream.h"
|
||||||
|
|
||||||
#include <fstream>
|
|
||||||
#include <streambuf>
|
#include <streambuf>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
@ -15,7 +15,7 @@ namespace IoUtilities {
|
||||||
*/
|
*/
|
||||||
string readFile(const string &path, std::string::size_type maxSize)
|
string readFile(const string &path, std::string::size_type maxSize)
|
||||||
{
|
{
|
||||||
ifstream file;
|
NativeFileStream file;
|
||||||
file.exceptions(ios_base::failbit | ios_base::badbit);
|
file.exceptions(ios_base::failbit | ios_base::badbit);
|
||||||
file.open(path, ios_base::in | ios_base::binary);
|
file.open(path, ios_base::in | ios_base::binary);
|
||||||
file.seekg(0, ios_base::end);
|
file.seekg(0, ios_base::end);
|
||||||
|
|
|
@ -1,44 +1,72 @@
|
||||||
#include "./nativefilestream.h"
|
#include "./nativefilestream.h"
|
||||||
|
|
||||||
#if defined(CPP_UTILITIES_USE_NATIVE_FILE_BUFFER) && (defined(PLATFORM_MINGW) || defined(PLATFORM_LINUX))
|
#ifdef CPP_UTILITIES_USE_NATIVE_FILE_BUFFER
|
||||||
|
|
||||||
#include "./catchiofailure.h"
|
#include "./catchiofailure.h"
|
||||||
|
|
||||||
#ifdef PLATFORM_MINGW
|
// include header files for file buffer implementation
|
||||||
|
#if defined(CPP_UTILITIES_USE_GNU_CXX_STDIO_FILEBUF)
|
||||||
|
#include <ext/stdio_filebuf.h>
|
||||||
|
#elif defined(CPP_UTILITIES_USE_BOOST_IOSTREAMS)
|
||||||
|
#include <boost/iostreams/device/file_descriptor.hpp>
|
||||||
|
#include <boost/iostreams/stream.hpp>
|
||||||
|
#else
|
||||||
|
#error "Configuration for NativeFileStream backend insufficient."
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// include platform specific header
|
||||||
|
#if defined(PLATFORM_UNIX)
|
||||||
|
#include <cstdio>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#elif defined(PLATFORM_WINDOWS)
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
#include <io.h>
|
#include <io.h>
|
||||||
#include <sys/stat.h> // yes, this is needed under Windows (https://msdn.microsoft.com/en-US/library/5yhhz3y7.aspx)
|
#include <sys/stat.h> // yes, this is needed under Windows (https://msdn.microsoft.com/en-US/library/5yhhz3y7.aspx)
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef PLATFORM_LINUX
|
|
||||||
#include <cstdio>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
namespace IoUtilities {
|
namespace IoUtilities {
|
||||||
|
|
||||||
#if defined(CPP_UTILITIES_USE_NATIVE_FILE_BUFFER) && (defined(PLATFORM_MINGW) || defined(PLATFORM_UNIX))
|
#ifdef CPP_UTILITIES_USE_NATIVE_FILE_BUFFER
|
||||||
|
|
||||||
|
#ifdef CPP_UTILITIES_USE_GNU_CXX_STDIO_FILEBUF
|
||||||
|
using StreamBuffer = __gnu_cxx::stdio_filebuf<char>;
|
||||||
|
#else // CPP_UTILITIES_USE_BOOST_IOSTREAMS
|
||||||
|
using StreamBuffer = boost::iostreams::stream_buffer<boost::iostreams::file_descriptor>;
|
||||||
|
#endif
|
||||||
|
|
||||||
struct NativeFileParams {
|
struct NativeFileParams {
|
||||||
|
|
||||||
#ifdef PLATFORM_MINGW
|
#ifdef PLATFORM_WINDOWS
|
||||||
NativeFileParams(ios_base::openmode cppOpenMode)
|
NativeFileParams(ios_base::openmode cppOpenMode)
|
||||||
: openMode(cppOpenMode & ios_base::binary ? _O_BINARY : 0)
|
: openMode(cppOpenMode & ios_base::binary ? _O_BINARY : 0)
|
||||||
, flags(cppOpenMode & ios_base::binary ? 0 : _O_TEXT)
|
, flags(cppOpenMode & ios_base::binary ? 0 : _O_TEXT)
|
||||||
, permissions(0)
|
, permissions(0)
|
||||||
|
, access(0)
|
||||||
|
, shareMode(0)
|
||||||
|
, creation(0)
|
||||||
{
|
{
|
||||||
if ((cppOpenMode & ios_base::out) && (cppOpenMode & ios_base::in)) {
|
if ((cppOpenMode & ios_base::out) && (cppOpenMode & ios_base::in)) {
|
||||||
openMode |= _O_RDWR;
|
openMode |= _O_RDWR;
|
||||||
|
access = GENERIC_READ | GENERIC_WRITE;
|
||||||
|
shareMode = FILE_SHARE_READ;
|
||||||
|
creation = OPEN_EXISTING;
|
||||||
} else if (cppOpenMode & ios_base::out) {
|
} else if (cppOpenMode & ios_base::out) {
|
||||||
openMode |= _O_WRONLY | _O_CREAT;
|
openMode |= _O_WRONLY | _O_CREAT;
|
||||||
permissions = _S_IREAD | _S_IWRITE;
|
permissions = _S_IREAD | _S_IWRITE;
|
||||||
|
access = GENERIC_WRITE;
|
||||||
|
creation = OPEN_ALWAYS;
|
||||||
} else if (cppOpenMode & ios_base::in) {
|
} else if (cppOpenMode & ios_base::in) {
|
||||||
openMode |= _O_RDONLY;
|
openMode |= _O_RDONLY;
|
||||||
flags |= _O_RDONLY;
|
flags |= _O_RDONLY;
|
||||||
|
access = GENERIC_READ;
|
||||||
|
shareMode = FILE_SHARE_READ;
|
||||||
|
creation = OPEN_EXISTING;
|
||||||
}
|
}
|
||||||
if (cppOpenMode & ios_base::app) {
|
if (cppOpenMode & ios_base::app) {
|
||||||
openMode |= _O_APPEND;
|
openMode |= _O_APPEND;
|
||||||
|
@ -46,33 +74,44 @@ struct NativeFileParams {
|
||||||
}
|
}
|
||||||
if (cppOpenMode & ios_base::trunc) {
|
if (cppOpenMode & ios_base::trunc) {
|
||||||
openMode |= _O_TRUNC;
|
openMode |= _O_TRUNC;
|
||||||
|
creation = (cppOpenMode & ios_base::in) ? TRUNCATE_EXISTING : CREATE_ALWAYS;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int openMode;
|
int openMode;
|
||||||
int flags;
|
int flags;
|
||||||
int permissions;
|
int permissions;
|
||||||
|
DWORD access;
|
||||||
|
DWORD shareMode;
|
||||||
|
DWORD creation;
|
||||||
#else
|
#else
|
||||||
NativeFileParams(ios_base::openmode cppOpenMode)
|
NativeFileParams(ios_base::openmode cppOpenMode)
|
||||||
|
: openFlags(0)
|
||||||
{
|
{
|
||||||
if ((cppOpenMode & ios_base::in) && (cppOpenMode & ios_base::out)) {
|
if ((cppOpenMode & ios_base::in) && (cppOpenMode & ios_base::out)) {
|
||||||
if (cppOpenMode & ios_base::app) {
|
if (cppOpenMode & ios_base::app) {
|
||||||
openMode = "a+";
|
openMode = "a+";
|
||||||
|
openFlags = O_RDWR | O_APPEND;
|
||||||
} else if (cppOpenMode & ios_base::trunc) {
|
} else if (cppOpenMode & ios_base::trunc) {
|
||||||
openMode = "w+";
|
openMode = "w+";
|
||||||
|
openFlags = O_RDWR | O_TRUNC;
|
||||||
} else {
|
} else {
|
||||||
openMode = "r+";
|
openMode = "r+";
|
||||||
|
openFlags = O_RDWR;
|
||||||
}
|
}
|
||||||
} else if (cppOpenMode & ios_base::in) {
|
} else if (cppOpenMode & ios_base::in) {
|
||||||
openMode = 'r';
|
openMode = 'r';
|
||||||
|
openFlags = O_RDONLY;
|
||||||
} else if (cppOpenMode & ios_base::out) {
|
} else if (cppOpenMode & ios_base::out) {
|
||||||
if (cppOpenMode & ios_base::app) {
|
if (cppOpenMode & ios_base::app) {
|
||||||
openMode = 'a';
|
openMode = 'a';
|
||||||
|
openFlags = O_WRONLY | O_APPEND;
|
||||||
} else if (cppOpenMode & ios_base::trunc) {
|
} else if (cppOpenMode & ios_base::trunc) {
|
||||||
openMode = 'w';
|
openMode = 'w';
|
||||||
|
openFlags = O_WRONLY | O_TRUNC | O_CREAT;
|
||||||
} else {
|
} else {
|
||||||
openMode = "r+";
|
openMode = "r+";
|
||||||
|
openFlags = O_WRONLY | O_CREAT;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (cppOpenMode & ios_base::binary) {
|
if (cppOpenMode & ios_base::binary) {
|
||||||
|
@ -81,62 +120,152 @@ struct NativeFileParams {
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string openMode;
|
std::string openMode;
|
||||||
|
int openFlags;
|
||||||
#endif
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Constructs a new NativeFileStream which is initially closed.
|
||||||
|
*/
|
||||||
NativeFileStream::NativeFileStream()
|
NativeFileStream::NativeFileStream()
|
||||||
: m_filebuf(new __gnu_cxx::stdio_filebuf<char>)
|
: m_filebuf(make_unique<StreamBuffer>())
|
||||||
{
|
{
|
||||||
rdbuf(m_filebuf.get());
|
init(m_filebuf.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Moves the NativeFileStream.
|
||||||
|
*/
|
||||||
NativeFileStream::NativeFileStream(NativeFileStream &&other)
|
NativeFileStream::NativeFileStream(NativeFileStream &&other)
|
||||||
: m_filebuf(std::move(other.m_filebuf))
|
: m_filebuf(std::move(other.m_filebuf))
|
||||||
, m_cfile(other.m_cfile)
|
, m_fileHandle(other.m_fileHandle)
|
||||||
{
|
{
|
||||||
|
init(m_filebuf.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Destroys the NativeFileStream releasing all underlying resources.
|
||||||
|
*/
|
||||||
NativeFileStream::~NativeFileStream()
|
NativeFileStream::~NativeFileStream()
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Returns whether the file is open.
|
||||||
|
*/
|
||||||
|
bool NativeFileStream::is_open() const
|
||||||
|
{
|
||||||
|
return m_filebuf && static_cast<const StreamBuffer *>(m_filebuf.get())->is_open();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Opens the file referenced by \a path with the specified \a openMode.
|
||||||
|
* \remarks
|
||||||
|
* Under Windows \a path is expected to be UTF-8 encoded. It is automatically converted so non-ASCII
|
||||||
|
* characters are treated correctly under Windows (in contrast to std::fstream::open() where only the
|
||||||
|
* current code page is supported).
|
||||||
|
*
|
||||||
|
* Under other platforms the \a path is just passed through so there are no assumptions made about its
|
||||||
|
* encoding.
|
||||||
|
* \throws Throws std::ios_base::failure in the error case.
|
||||||
|
* \todo Maybe use setstate() instead of throwing exceptions directly for consistent error handling
|
||||||
|
* with std::fstream::open(). However, that makes passing specific error messages difficult.
|
||||||
|
*/
|
||||||
void NativeFileStream::open(const string &path, ios_base::openmode openMode)
|
void NativeFileStream::open(const string &path, ios_base::openmode openMode)
|
||||||
{
|
{
|
||||||
#ifdef PLATFORM_MINGW
|
setFileBuffer(makeFileBuffer(path, openMode));
|
||||||
// convert path to UTF-16
|
}
|
||||||
int requiredSize = MultiByteToWideChar(CP_UTF8, 0, path.data(), -1, nullptr, 0);
|
|
||||||
if (requiredSize <= 0) {
|
|
||||||
::IoUtilities::throwIoFailure("Unable to calculate buffer size for conversion of path to UTF-16");
|
|
||||||
}
|
|
||||||
auto widePath = make_unique<wchar_t[]>(static_cast<size_t>(requiredSize));
|
|
||||||
requiredSize = MultiByteToWideChar(CP_UTF8, 0, path.data(), -1, widePath.get(), requiredSize);
|
|
||||||
if (requiredSize <= 0) {
|
|
||||||
::IoUtilities::throwIoFailure("Unable to convert path to UTF-16");
|
|
||||||
}
|
|
||||||
|
|
||||||
// initialize stdio_filebuf
|
/*!
|
||||||
|
* \brief Opens the file from the specified \a fileDescriptor with the specified \a openMode.
|
||||||
|
* \throws Throws std::ios_base::failure in the error case.
|
||||||
|
* \todo
|
||||||
|
* - Maybe use setstate() instead of throwing exceptions directly for consistent error handling
|
||||||
|
* with std::fstream::open(). However, that makes passing specific error messages difficult.
|
||||||
|
* - Rename to open() in v5.
|
||||||
|
*/
|
||||||
|
void NativeFileStream::openFromFileDescriptor(int fileDescriptor, ios_base::openmode openMode)
|
||||||
|
{
|
||||||
|
setFileBuffer(makeFileBuffer(fileDescriptor, openMode));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Closes the file if opened; otherwise does nothing.
|
||||||
|
*/
|
||||||
|
void NativeFileStream::close()
|
||||||
|
{
|
||||||
|
if (m_filebuf) {
|
||||||
|
static_cast<StreamBuffer *>(m_filebuf.get())->close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief Internally called to assign the \a buffer taking. Takes ownership over \a buffer.
|
||||||
|
*/
|
||||||
|
void NativeFileStream::setFileBuffer(std::unique_ptr<std::basic_streambuf<char>> buffer)
|
||||||
|
{
|
||||||
|
rdbuf(buffer.get());
|
||||||
|
m_filebuf = std::move(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
* \brief \brief Internally called by open().
|
||||||
|
*/
|
||||||
|
std::unique_ptr<std::basic_streambuf<char>> NativeFileStream::makeFileBuffer(const string &path, ios_base::openmode openMode)
|
||||||
|
{
|
||||||
|
#ifdef PLATFORM_WINDOWS
|
||||||
|
// convert path to UTF-16
|
||||||
|
const auto widePath(makeWidePath(path));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// compute native params
|
||||||
const NativeFileParams nativeParams(openMode);
|
const NativeFileParams nativeParams(openMode);
|
||||||
|
|
||||||
|
#ifdef CPP_UTILITIES_USE_GNU_CXX_STDIO_FILEBUF
|
||||||
|
// open file handle to initialize stdio_filebuf
|
||||||
|
#ifdef PLATFORM_WINDOWS
|
||||||
const int fileHandle = _wopen(widePath.get(), nativeParams.openMode, nativeParams.permissions);
|
const int fileHandle = _wopen(widePath.get(), nativeParams.openMode, nativeParams.permissions);
|
||||||
if (fileHandle == -1) {
|
if (fileHandle == -1) {
|
||||||
::IoUtilities::throwIoFailure("_wopen failed");
|
::IoUtilities::throwIoFailure("_wopen failed");
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
|
|
||||||
// initialize stdio_filebuf
|
|
||||||
const NativeFileParams nativeParams(openMode);
|
|
||||||
const auto fileHandle = fopen(path.data(), nativeParams.openMode.data());
|
const auto fileHandle = fopen(path.data(), nativeParams.openMode.data());
|
||||||
if (!fileHandle) {
|
if (!fileHandle) {
|
||||||
::IoUtilities::throwIoFailure("fopen failed");
|
::IoUtilities::throwIoFailure("fopen failed");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
m_filebuf = make_unique<__gnu_cxx::stdio_filebuf<char>>(fileHandle, openMode);
|
return make_unique<StreamBuffer>(fileHandle, openMode);
|
||||||
rdbuf(m_filebuf.get());
|
|
||||||
|
#else // CPP_UTILITIES_USE_BOOST_IOSTREAMS
|
||||||
|
// create raw file descriptor to initialize boost::iostreams::file_descriptor
|
||||||
|
#ifdef PLATFORM_WINDOWS
|
||||||
|
const auto fileDescriptor
|
||||||
|
= CreateFileW(widePath.get(), nativeParams.access, nativeParams.shareMode, nullptr, nativeParams.creation, FILE_ATTRIBUTE_NORMAL);
|
||||||
|
if (fileDescriptor == INVALID_HANDLE_VALUE) {
|
||||||
|
::IoUtilities::throwIoFailure("CreateFileW failed");
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
const auto fileDescriptor = ::open(path.data(), nativeParams.openFlags);
|
||||||
|
if (fileDescriptor == -1) {
|
||||||
|
::IoUtilities::throwIoFailure("open failed");
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return make_unique<StreamBuffer>(fileDescriptor, boost::iostreams::close_handle);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void NativeFileStream::openFromFileDescriptor(int fileDescriptor, ios_base::openmode openMode)
|
/*!
|
||||||
|
* \brief Internally called by openFromFileDescriptor().
|
||||||
|
*/
|
||||||
|
std::unique_ptr<std::basic_streambuf<char>> NativeFileStream::makeFileBuffer(int fileDescriptor, ios_base::openmode openMode)
|
||||||
{
|
{
|
||||||
|
// compute native params
|
||||||
const NativeFileParams nativeParams(openMode);
|
const NativeFileParams nativeParams(openMode);
|
||||||
#ifdef PLATFORM_MINGW
|
|
||||||
|
|
||||||
|
#ifdef CPP_UTILITIES_USE_GNU_CXX_STDIO_FILEBUF
|
||||||
|
// open file handle to initialize stdio_filebuf
|
||||||
|
#ifdef PLATFORM_WINDOWS
|
||||||
const auto fileHandle = _get_osfhandle(fileDescriptor);
|
const auto fileHandle = _get_osfhandle(fileDescriptor);
|
||||||
if (fileHandle == -1) {
|
if (fileHandle == -1) {
|
||||||
::IoUtilities::throwIoFailure("_get_osfhandle failed");
|
::IoUtilities::throwIoFailure("_get_osfhandle failed");
|
||||||
|
@ -151,16 +280,29 @@ void NativeFileStream::openFromFileDescriptor(int fileDescriptor, ios_base::open
|
||||||
::IoUtilities::throwIoFailure("fdopen failed");
|
::IoUtilities::throwIoFailure("fdopen failed");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
m_filebuf = make_unique<__gnu_cxx::stdio_filebuf<char>>(fileHandle, openMode);
|
return make_unique<StreamBuffer>(fileDescriptor, openMode);
|
||||||
rdbuf(m_filebuf.get());
|
|
||||||
|
#else // CPP_UTILITIES_USE_BOOST_IOSTREAMS
|
||||||
|
// initialize boost::iostreams::file_descriptor from the specified fileDescriptor
|
||||||
|
return make_unique<StreamBuffer>(fileDescriptor, boost::iostreams::close_handle);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void NativeFileStream::close()
|
#ifdef PLATFORM_WINDOWS
|
||||||
|
std::unique_ptr<wchar_t[]> NativeFileStream::makeWidePath(const std::string &path)
|
||||||
{
|
{
|
||||||
if (m_filebuf) {
|
int requiredSize = MultiByteToWideChar(CP_UTF8, 0, path.data(), -1, nullptr, 0);
|
||||||
m_filebuf->close();
|
if (requiredSize <= 0) {
|
||||||
|
::IoUtilities::throwIoFailure("Unable to calculate buffer size for conversion of path to UTF-16");
|
||||||
}
|
}
|
||||||
|
auto widePath = make_unique<wchar_t[]>(static_cast<size_t>(requiredSize));
|
||||||
|
requiredSize = MultiByteToWideChar(CP_UTF8, 0, path.data(), -1, widePath.get(), requiredSize);
|
||||||
|
if (requiredSize <= 0) {
|
||||||
|
::IoUtilities::throwIoFailure("Unable to convert path to UTF-16");
|
||||||
|
}
|
||||||
|
return widePath;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
|
|
|
@ -3,27 +3,24 @@
|
||||||
|
|
||||||
#include "../global.h"
|
#include "../global.h"
|
||||||
|
|
||||||
#if defined(CPP_UTILITIES_USE_NATIVE_FILE_BUFFER)
|
#ifdef CPP_UTILITIES_USE_NATIVE_FILE_BUFFER
|
||||||
#if defined(PLATFORM_MINGW) || defined(PLATFORM_LINUX)
|
|
||||||
#include <ext/stdio_filebuf.h>
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <streambuf>
|
||||||
#include <string>
|
#include <string>
|
||||||
#else
|
#else
|
||||||
#error "Platform not supported by NativeFileStream."
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#else
|
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace IoUtilities {
|
namespace IoUtilities {
|
||||||
|
|
||||||
#if defined(CPP_UTILITIES_USE_NATIVE_FILE_BUFFER) && (defined(PLATFORM_MINGW) || defined(PLATFORM_UNIX))
|
#ifdef CPP_UTILITIES_USE_NATIVE_FILE_BUFFER
|
||||||
|
|
||||||
class CPP_UTILITIES_EXPORT NativeFileStream : public std::iostream {
|
class CPP_UTILITIES_EXPORT NativeFileStream : public std::iostream {
|
||||||
public:
|
public:
|
||||||
NativeFileStream();
|
NativeFileStream();
|
||||||
|
NativeFileStream(const std::string &path, std::ios_base::openmode openMode);
|
||||||
|
NativeFileStream(int fileDescriptor, std::ios_base::openmode openMode);
|
||||||
NativeFileStream(NativeFileStream &&);
|
NativeFileStream(NativeFileStream &&);
|
||||||
~NativeFileStream();
|
~NativeFileStream();
|
||||||
|
|
||||||
|
@ -31,24 +28,42 @@ public:
|
||||||
void open(const std::string &path, std::ios_base::openmode openMode);
|
void open(const std::string &path, std::ios_base::openmode openMode);
|
||||||
void openFromFileDescriptor(int fileDescriptor, std::ios_base::openmode openMode);
|
void openFromFileDescriptor(int fileDescriptor, std::ios_base::openmode openMode);
|
||||||
void close();
|
void close();
|
||||||
std::__c_file fileHandle();
|
FILE fileHandle();
|
||||||
|
|
||||||
|
static std::unique_ptr<std::basic_streambuf<char>> makeFileBuffer(const std::string &path, ios_base::openmode openMode);
|
||||||
|
static std::unique_ptr<std::basic_streambuf<char>> makeFileBuffer(int fileDescriptor, ios_base::openmode openMode);
|
||||||
|
#ifdef PLATFORM_WINDOWS
|
||||||
|
static std::unique_ptr<wchar_t[]> makeWidePath(const std::string &path);
|
||||||
|
#endif
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unique_ptr<__gnu_cxx::stdio_filebuf<char>> m_filebuf;
|
void setFileBuffer(std::unique_ptr<std::basic_streambuf<char>> buffer);
|
||||||
std::__c_file m_cfile;
|
|
||||||
|
std::unique_ptr<std::basic_streambuf<char>> m_filebuf;
|
||||||
|
FILE m_fileHandle;
|
||||||
};
|
};
|
||||||
|
|
||||||
inline bool NativeFileStream::is_open() const
|
inline NativeFileStream::NativeFileStream(const std::string &path, ios_base::openmode openMode)
|
||||||
{
|
{
|
||||||
return m_filebuf && m_filebuf->is_open();
|
open(path, openMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
inline std::__c_file NativeFileStream::fileHandle()
|
inline NativeFileStream::NativeFileStream(int fileDescriptor, ios_base::openmode openMode)
|
||||||
{
|
{
|
||||||
return m_cfile;
|
openFromFileDescriptor(fileDescriptor, openMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
#else
|
/*!
|
||||||
|
* \brief Returns the underlying file handle if possible; otherwise the behaviour is undefined.
|
||||||
|
* \deprecated Not implemented for any backend and will be removed in v5.
|
||||||
|
* \todo Remove in v5.
|
||||||
|
*/
|
||||||
|
inline FILE NativeFileStream::fileHandle()
|
||||||
|
{
|
||||||
|
return m_fileHandle;
|
||||||
|
}
|
||||||
|
|
||||||
|
#else // CPP_UTILITIES_USE_NATIVE_FILE_BUFFER
|
||||||
|
|
||||||
using NativeFileStream = std::fstream;
|
using NativeFileStream = std::fstream;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
file with non-ASCII character 'ä' in its name
|
|
@ -1,6 +1,8 @@
|
||||||
#include "./testutils.h"
|
#include "./testutils.h"
|
||||||
|
|
||||||
#include "../conversion/conversionexception.h"
|
#include "../conversion/conversionexception.h"
|
||||||
|
#include "../conversion/stringbuilder.h"
|
||||||
|
|
||||||
#include "../io/ansiescapecodes.h"
|
#include "../io/ansiescapecodes.h"
|
||||||
#include "../io/binaryreader.h"
|
#include "../io/binaryreader.h"
|
||||||
#include "../io/binarywriter.h"
|
#include "../io/binarywriter.h"
|
||||||
|
@ -9,6 +11,7 @@
|
||||||
#include "../io/copy.h"
|
#include "../io/copy.h"
|
||||||
#include "../io/inifile.h"
|
#include "../io/inifile.h"
|
||||||
#include "../io/misc.h"
|
#include "../io/misc.h"
|
||||||
|
#include "../io/nativefilestream.h"
|
||||||
#include "../io/path.h"
|
#include "../io/path.h"
|
||||||
|
|
||||||
#include <cppunit/TestFixture.h>
|
#include <cppunit/TestFixture.h>
|
||||||
|
@ -16,8 +19,14 @@
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
#include <regex>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
|
#ifdef PLATFORM_UNIX
|
||||||
|
#include <sys/fcntl.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
using namespace IoUtilities;
|
using namespace IoUtilities;
|
||||||
using namespace TestUtilities;
|
using namespace TestUtilities;
|
||||||
|
@ -38,8 +47,11 @@ class IoTests : public TestFixture {
|
||||||
CPPUNIT_TEST(testPathUtilities);
|
CPPUNIT_TEST(testPathUtilities);
|
||||||
CPPUNIT_TEST(testIniFile);
|
CPPUNIT_TEST(testIniFile);
|
||||||
CPPUNIT_TEST(testCopy);
|
CPPUNIT_TEST(testCopy);
|
||||||
CPPUNIT_TEST(testMisc);
|
CPPUNIT_TEST(testReadFile);
|
||||||
CPPUNIT_TEST(testAnsiEscapeCodes);
|
CPPUNIT_TEST(testAnsiEscapeCodes);
|
||||||
|
#ifdef CPP_UTILITIES_USE_NATIVE_FILE_BUFFER
|
||||||
|
CPPUNIT_TEST(testNativeFileStream);
|
||||||
|
#endif
|
||||||
CPPUNIT_TEST_SUITE_END();
|
CPPUNIT_TEST_SUITE_END();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
@ -53,8 +65,9 @@ public:
|
||||||
void testPathUtilities();
|
void testPathUtilities();
|
||||||
void testIniFile();
|
void testIniFile();
|
||||||
void testCopy();
|
void testCopy();
|
||||||
void testMisc();
|
void testReadFile();
|
||||||
void testAnsiEscapeCodes();
|
void testAnsiEscapeCodes();
|
||||||
|
void testNativeFileStream();
|
||||||
};
|
};
|
||||||
|
|
||||||
CPPUNIT_TEST_SUITE_REGISTRATION(IoTests);
|
CPPUNIT_TEST_SUITE_REGISTRATION(IoTests);
|
||||||
|
@ -383,10 +396,11 @@ void IoTests::testCopy()
|
||||||
}
|
}
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* \brief Tests misc IO utilities.
|
* \brief Tests readFile().
|
||||||
*/
|
*/
|
||||||
void IoTests::testMisc()
|
void IoTests::testReadFile()
|
||||||
{
|
{
|
||||||
|
// read a file successfully
|
||||||
const string iniFilePath(testFilePath("test.ini"));
|
const string iniFilePath(testFilePath("test.ini"));
|
||||||
CPPUNIT_ASSERT_EQUAL("# file for testing INI parser\n"
|
CPPUNIT_ASSERT_EQUAL("# file for testing INI parser\n"
|
||||||
"key0=value 0\n"
|
"key0=value 0\n"
|
||||||
|
@ -401,12 +415,19 @@ void IoTests::testMisc()
|
||||||
"#key5=value 5\n"
|
"#key5=value 5\n"
|
||||||
"key6=value 6\n"s,
|
"key6=value 6\n"s,
|
||||||
readFile(iniFilePath));
|
readFile(iniFilePath));
|
||||||
|
|
||||||
|
// fail by exceeding max size
|
||||||
try {
|
try {
|
||||||
readFile(iniFilePath, 10);
|
readFile(iniFilePath, 10);
|
||||||
CPPUNIT_FAIL("no exception");
|
CPPUNIT_FAIL("no exception");
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
catchIoFailure();
|
catchIoFailure();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handle UTF-8 in path and file contents correctly via NativeFileStream
|
||||||
|
#if !defined(PLATFORM_WINDOWS) || defined(CPP_UTILITIES_USE_NATIVE_FILE_BUFFER)
|
||||||
|
CPPUNIT_ASSERT_EQUAL("file with non-ASCII character 'ä' in its name\n"s, readFile(testFilePath("täst.txt")));
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
void IoTests::testAnsiEscapeCodes()
|
void IoTests::testAnsiEscapeCodes()
|
||||||
|
@ -441,3 +462,89 @@ void IoTests::testAnsiEscapeCodes()
|
||||||
ss2 << EscapeCodes::Phrases::Info << "some info" << EscapeCodes::Phrases::End;
|
ss2 << EscapeCodes::Phrases::Info << "some info" << EscapeCodes::Phrases::End;
|
||||||
CPPUNIT_ASSERT_EQUAL("Info: some info\n"s, ss2.str());
|
CPPUNIT_ASSERT_EQUAL("Info: some info\n"s, ss2.str());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef CPP_UTILITIES_USE_NATIVE_FILE_BUFFER
|
||||||
|
/*!
|
||||||
|
* \brief Tests the NativeFileStream class.
|
||||||
|
*/
|
||||||
|
void IoTests::testNativeFileStream()
|
||||||
|
{
|
||||||
|
// open file by path
|
||||||
|
const auto txtFilePath(workingCopyPath("täst.txt"));
|
||||||
|
NativeFileStream fileStream;
|
||||||
|
fileStream.exceptions(ios_base::badbit | ios_base::failbit);
|
||||||
|
CPPUNIT_ASSERT(!fileStream.is_open());
|
||||||
|
fileStream.open(txtFilePath, ios_base::in);
|
||||||
|
CPPUNIT_ASSERT(fileStream.is_open());
|
||||||
|
CPPUNIT_ASSERT_EQUAL(static_cast<char>(fileStream.get()), 'f');
|
||||||
|
fileStream.seekg(0, ios_base::end);
|
||||||
|
CPPUNIT_ASSERT_EQUAL(fileStream.tellg(), static_cast<NativeFileStream::pos_type>(47));
|
||||||
|
fileStream.close();
|
||||||
|
CPPUNIT_ASSERT(!fileStream.is_open());
|
||||||
|
try {
|
||||||
|
fileStream.open("non existing file", ios_base::in | ios_base::out | ios_base::binary);
|
||||||
|
CPPUNIT_FAIL("expected exception");
|
||||||
|
} catch (...) {
|
||||||
|
const string msg = catchIoFailure();
|
||||||
|
#if defined(PLATFORM_UNIX)
|
||||||
|
CPPUNIT_ASSERT_EQUAL(msg, "open failed: iostream error"s);
|
||||||
|
#elif defined(PLATFORM_WINDOWS)
|
||||||
|
CPPUNIT_ASSERT_EQUAL(msg, "CreateFileW failed: iostream error"s);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
fileStream.clear();
|
||||||
|
|
||||||
|
// open file from file descriptor
|
||||||
|
#if defined(PLATFORM_UNIX)
|
||||||
|
auto readWriteFileDescriptor = open(txtFilePath.data(), O_RDWR);
|
||||||
|
CPPUNIT_ASSERT(readWriteFileDescriptor);
|
||||||
|
fileStream.openFromFileDescriptor(readWriteFileDescriptor, ios_base::in | ios_base::out | ios_base::binary);
|
||||||
|
CPPUNIT_ASSERT(fileStream.is_open());
|
||||||
|
CPPUNIT_ASSERT_EQUAL(static_cast<char>(fileStream.get()), 'f');
|
||||||
|
fileStream.seekg(0, ios_base::end);
|
||||||
|
CPPUNIT_ASSERT_EQUAL(fileStream.tellg(), static_cast<NativeFileStream::pos_type>(47));
|
||||||
|
fileStream.flush();
|
||||||
|
fileStream.close();
|
||||||
|
CPPUNIT_ASSERT(!fileStream.is_open());
|
||||||
|
#endif
|
||||||
|
try {
|
||||||
|
fileStream.openFromFileDescriptor(-1, ios_base::in | ios_base::out | ios_base::binary);
|
||||||
|
fileStream.get();
|
||||||
|
CPPUNIT_FAIL("expected exception");
|
||||||
|
} catch (...) {
|
||||||
|
const string msg = catchIoFailure();
|
||||||
|
TESTUTILS_ASSERT_LIKE("expected error message", "(fdopen failed|failed reading: Bad file descriptor): iostream error", msg);
|
||||||
|
}
|
||||||
|
fileStream.clear();
|
||||||
|
|
||||||
|
// append + write file via path
|
||||||
|
cout << "append + write file via path" << endl;
|
||||||
|
NativeFileStream fileStream2;
|
||||||
|
fileStream2.exceptions(ios_base::failbit | ios_base::badbit);
|
||||||
|
fileStream2.open(txtFilePath, ios_base::in | ios_base::out | ios_base::app);
|
||||||
|
CPPUNIT_ASSERT(fileStream2.is_open());
|
||||||
|
fileStream2 << "foo";
|
||||||
|
fileStream2.flush();
|
||||||
|
fileStream2.close();
|
||||||
|
CPPUNIT_ASSERT(!fileStream2.is_open());
|
||||||
|
CPPUNIT_ASSERT_EQUAL("file with non-ASCII character 'ä' in its name\nfoo"s, readFile(txtFilePath, 50));
|
||||||
|
|
||||||
|
// truncate + write file via path
|
||||||
|
fileStream2.open(txtFilePath, ios_base::out | ios_base::trunc);
|
||||||
|
CPPUNIT_ASSERT(fileStream2.is_open());
|
||||||
|
fileStream2 << "bar";
|
||||||
|
fileStream2.close();
|
||||||
|
CPPUNIT_ASSERT(!fileStream2.is_open());
|
||||||
|
CPPUNIT_ASSERT_EQUAL("bar"s, readFile(txtFilePath, 4));
|
||||||
|
|
||||||
|
// append + write via file descriptor from file handle
|
||||||
|
const auto appendFileHandle = fopen(txtFilePath.data(), "a");
|
||||||
|
CPPUNIT_ASSERT(appendFileHandle);
|
||||||
|
fileStream2.openFromFileDescriptor(fileno(appendFileHandle), ios_base::out | ios_base::app);
|
||||||
|
CPPUNIT_ASSERT(fileStream2.is_open());
|
||||||
|
fileStream2 << "foo";
|
||||||
|
fileStream2.close();
|
||||||
|
CPPUNIT_ASSERT(!fileStream2.is_open());
|
||||||
|
CPPUNIT_ASSERT_EQUAL("barfoo"s, readFile(txtFilePath, 7));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
Loading…
Reference in New Issue