From 749eea2ab6445ccf5f9b0fbf5087abf1cc634d1e Mon Sep 17 00:00:00 2001 From: Martchus Date: Sat, 29 Sep 2018 18:05:02 +0200 Subject: [PATCH] Support NativeFileStream via Boost.Iostreams So it can also be enabled when not using libstdc++. --- CMakeLists.txt | 27 +++- cmake/modules/3rdParty.cmake | 2 +- feature_detection/stdio_filebuf.cpp | 10 ++ io/misc.cpp | 4 +- io/nativefilestream.cpp | 216 +++++++++++++++++++++++----- io/nativefilestream.h | 47 +++--- testfiles/täst.txt | 1 + tests/iotests.cpp | 115 ++++++++++++++- 8 files changed, 361 insertions(+), 61 deletions(-) create mode 100644 feature_detection/stdio_filebuf.cpp create mode 100644 testfiles/täst.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index b922874..9290e70 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -143,10 +143,35 @@ set(META_VERSION_PATCH 1) include(3rdParty) 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(FORCE_BOOST_IOSTREAMS_FOR_NATIVE_FILE_BUFFER "forces use of Boost.Iostreams for native file buffer" OFF) if(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 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() # configure forcing UTF-8 code page under Windows diff --git a/cmake/modules/3rdParty.cmake b/cmake/modules/3rdParty.cmake index 2871b23..39c7eae 100644 --- a/cmake/modules/3rdParty.cmake +++ b/cmake/modules/3rdParty.cmake @@ -236,7 +236,7 @@ macro(find_external_library_from_package NAME PKGNAME VERSION INCLUDE_VAR LIBRAR endif() # 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 find_external_library("${NAME}" "${LINKAGE}" OPTIONAL) diff --git a/feature_detection/stdio_filebuf.cpp b/feature_detection/stdio_filebuf.cpp new file mode 100644 index 0000000..01d83ed --- /dev/null +++ b/feature_detection/stdio_filebuf.cpp @@ -0,0 +1,10 @@ +// assume __gnu_cxx::stdio_filebuf is available if this program compiles + +#include +#include + +int main() { + const int fd = 0; + __gnu_cxx::stdio_filebuf buf(fd, std::ios_base::in); + return buf.fd(); +} diff --git a/io/misc.cpp b/io/misc.cpp index be79c3f..1fb30ff 100644 --- a/io/misc.cpp +++ b/io/misc.cpp @@ -1,7 +1,7 @@ #include "./misc.h" #include "./catchiofailure.h" +#include "./nativefilestream.h" -#include #include using namespace std; @@ -15,7 +15,7 @@ namespace IoUtilities { */ string readFile(const string &path, std::string::size_type maxSize) { - ifstream file; + NativeFileStream file; file.exceptions(ios_base::failbit | ios_base::badbit); file.open(path, ios_base::in | ios_base::binary); file.seekg(0, ios_base::end); diff --git a/io/nativefilestream.cpp b/io/nativefilestream.cpp index 251e6cb..476f0af 100644 --- a/io/nativefilestream.cpp +++ b/io/nativefilestream.cpp @@ -1,44 +1,72 @@ #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" -#ifdef PLATFORM_MINGW +// include header files for file buffer implementation +#if defined(CPP_UTILITIES_USE_GNU_CXX_STDIO_FILEBUF) +#include +#elif defined(CPP_UTILITIES_USE_BOOST_IOSTREAMS) +#include +#include +#else +#error "Configuration for NativeFileStream backend insufficient." +#endif + +// include platform specific header +#if defined(PLATFORM_UNIX) +#include +#include +#include +#include +#elif defined(PLATFORM_WINDOWS) #include #include #include // yes, this is needed under Windows (https://msdn.microsoft.com/en-US/library/5yhhz3y7.aspx) #include #endif -#ifdef PLATFORM_LINUX -#include -#endif - #endif using namespace std; 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; +#else // CPP_UTILITIES_USE_BOOST_IOSTREAMS +using StreamBuffer = boost::iostreams::stream_buffer; +#endif struct NativeFileParams { -#ifdef PLATFORM_MINGW +#ifdef PLATFORM_WINDOWS NativeFileParams(ios_base::openmode cppOpenMode) : openMode(cppOpenMode & ios_base::binary ? _O_BINARY : 0) , flags(cppOpenMode & ios_base::binary ? 0 : _O_TEXT) , permissions(0) + , access(0) + , shareMode(0) + , creation(0) { if ((cppOpenMode & ios_base::out) && (cppOpenMode & ios_base::in)) { openMode |= _O_RDWR; + access = GENERIC_READ | GENERIC_WRITE; + shareMode = FILE_SHARE_READ; + creation = OPEN_EXISTING; } else if (cppOpenMode & ios_base::out) { openMode |= _O_WRONLY | _O_CREAT; permissions = _S_IREAD | _S_IWRITE; + access = GENERIC_WRITE; + creation = OPEN_ALWAYS; } else if (cppOpenMode & ios_base::in) { openMode |= _O_RDONLY; flags |= _O_RDONLY; + access = GENERIC_READ; + shareMode = FILE_SHARE_READ; + creation = OPEN_EXISTING; } if (cppOpenMode & ios_base::app) { openMode |= _O_APPEND; @@ -46,33 +74,44 @@ struct NativeFileParams { } if (cppOpenMode & ios_base::trunc) { openMode |= _O_TRUNC; + creation = (cppOpenMode & ios_base::in) ? TRUNCATE_EXISTING : CREATE_ALWAYS; } } int openMode; int flags; int permissions; - + DWORD access; + DWORD shareMode; + DWORD creation; #else NativeFileParams(ios_base::openmode cppOpenMode) + : openFlags(0) { if ((cppOpenMode & ios_base::in) && (cppOpenMode & ios_base::out)) { if (cppOpenMode & ios_base::app) { openMode = "a+"; + openFlags = O_RDWR | O_APPEND; } else if (cppOpenMode & ios_base::trunc) { openMode = "w+"; + openFlags = O_RDWR | O_TRUNC; } else { openMode = "r+"; + openFlags = O_RDWR; } } else if (cppOpenMode & ios_base::in) { openMode = 'r'; + openFlags = O_RDONLY; } else if (cppOpenMode & ios_base::out) { if (cppOpenMode & ios_base::app) { openMode = 'a'; + openFlags = O_WRONLY | O_APPEND; } else if (cppOpenMode & ios_base::trunc) { openMode = 'w'; + openFlags = O_WRONLY | O_TRUNC | O_CREAT; } else { openMode = "r+"; + openFlags = O_WRONLY | O_CREAT; } } if (cppOpenMode & ios_base::binary) { @@ -81,62 +120,152 @@ struct NativeFileParams { } std::string openMode; + int openFlags; #endif }; +/*! + * \brief Constructs a new NativeFileStream which is initially closed. + */ NativeFileStream::NativeFileStream() - : m_filebuf(new __gnu_cxx::stdio_filebuf) + : m_filebuf(make_unique()) { - rdbuf(m_filebuf.get()); + init(m_filebuf.get()); } +/*! + * \brief Moves the NativeFileStream. + */ NativeFileStream::NativeFileStream(NativeFileStream &&other) : 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() { } +/*! + * \brief Returns whether the file is open. + */ +bool NativeFileStream::is_open() const +{ + return m_filebuf && static_cast(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) { -#ifdef PLATFORM_MINGW - // 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(static_cast(requiredSize)); - requiredSize = MultiByteToWideChar(CP_UTF8, 0, path.data(), -1, widePath.get(), requiredSize); - if (requiredSize <= 0) { - ::IoUtilities::throwIoFailure("Unable to convert path to UTF-16"); - } + setFileBuffer(makeFileBuffer(path, openMode)); +} - // 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(m_filebuf.get())->close(); + } +} + +/*! + * \brief Internally called to assign the \a buffer taking. Takes ownership over \a buffer. + */ +void NativeFileStream::setFileBuffer(std::unique_ptr> buffer) +{ + rdbuf(buffer.get()); + m_filebuf = std::move(buffer); +} + +/*! + * \brief \brief Internally called by open(). + */ +std::unique_ptr> 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); + +#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); if (fileHandle == -1) { ::IoUtilities::throwIoFailure("_wopen failed"); } #else - - // initialize stdio_filebuf - const NativeFileParams nativeParams(openMode); const auto fileHandle = fopen(path.data(), nativeParams.openMode.data()); if (!fileHandle) { ::IoUtilities::throwIoFailure("fopen failed"); } #endif - m_filebuf = make_unique<__gnu_cxx::stdio_filebuf>(fileHandle, openMode); - rdbuf(m_filebuf.get()); + return make_unique(fileHandle, openMode); + +#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(fileDescriptor, boost::iostreams::close_handle); +#endif } -void NativeFileStream::openFromFileDescriptor(int fileDescriptor, ios_base::openmode openMode) +/*! + * \brief Internally called by openFromFileDescriptor(). + */ +std::unique_ptr> NativeFileStream::makeFileBuffer(int fileDescriptor, ios_base::openmode openMode) { + // compute native params 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); if (fileHandle == -1) { ::IoUtilities::throwIoFailure("_get_osfhandle failed"); @@ -151,16 +280,29 @@ void NativeFileStream::openFromFileDescriptor(int fileDescriptor, ios_base::open ::IoUtilities::throwIoFailure("fdopen failed"); } #endif - m_filebuf = make_unique<__gnu_cxx::stdio_filebuf>(fileHandle, openMode); - rdbuf(m_filebuf.get()); + return make_unique(fileDescriptor, openMode); + +#else // CPP_UTILITIES_USE_BOOST_IOSTREAMS + // initialize boost::iostreams::file_descriptor from the specified fileDescriptor + return make_unique(fileDescriptor, boost::iostreams::close_handle); +#endif } -void NativeFileStream::close() +#ifdef PLATFORM_WINDOWS +std::unique_ptr NativeFileStream::makeWidePath(const std::string &path) { - if (m_filebuf) { - m_filebuf->close(); + 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(static_cast(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 diff --git a/io/nativefilestream.h b/io/nativefilestream.h index ae3f3bc..35c25e8 100644 --- a/io/nativefilestream.h +++ b/io/nativefilestream.h @@ -3,27 +3,24 @@ #include "../global.h" -#if defined(CPP_UTILITIES_USE_NATIVE_FILE_BUFFER) -#if defined(PLATFORM_MINGW) || defined(PLATFORM_LINUX) -#include +#ifdef CPP_UTILITIES_USE_NATIVE_FILE_BUFFER #include #include +#include #include #else -#error "Platform not supported by NativeFileStream." #endif - -#else #include -#endif 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 { public: NativeFileStream(); + NativeFileStream(const std::string &path, std::ios_base::openmode openMode); + NativeFileStream(int fileDescriptor, std::ios_base::openmode openMode); NativeFileStream(NativeFileStream &&); ~NativeFileStream(); @@ -31,24 +28,42 @@ public: void open(const std::string &path, std::ios_base::openmode openMode); void openFromFileDescriptor(int fileDescriptor, std::ios_base::openmode openMode); void close(); - std::__c_file fileHandle(); + FILE fileHandle(); + + static std::unique_ptr> makeFileBuffer(const std::string &path, ios_base::openmode openMode); + static std::unique_ptr> makeFileBuffer(int fileDescriptor, ios_base::openmode openMode); +#ifdef PLATFORM_WINDOWS + static std::unique_ptr makeWidePath(const std::string &path); +#endif private: - std::unique_ptr<__gnu_cxx::stdio_filebuf> m_filebuf; - std::__c_file m_cfile; + void setFileBuffer(std::unique_ptr> buffer); + + std::unique_ptr> 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; diff --git a/testfiles/täst.txt b/testfiles/täst.txt new file mode 100644 index 0000000..e4ec7f8 --- /dev/null +++ b/testfiles/täst.txt @@ -0,0 +1 @@ +file with non-ASCII character 'ä' in its name diff --git a/tests/iotests.cpp b/tests/iotests.cpp index 24f36b9..c36841e 100644 --- a/tests/iotests.cpp +++ b/tests/iotests.cpp @@ -1,6 +1,8 @@ #include "./testutils.h" #include "../conversion/conversionexception.h" +#include "../conversion/stringbuilder.h" + #include "../io/ansiescapecodes.h" #include "../io/binaryreader.h" #include "../io/binarywriter.h" @@ -9,6 +11,7 @@ #include "../io/copy.h" #include "../io/inifile.h" #include "../io/misc.h" +#include "../io/nativefilestream.h" #include "../io/path.h" #include @@ -16,8 +19,14 @@ #include #include +#include #include +#ifdef PLATFORM_UNIX +#include +#include +#endif + using namespace std; using namespace IoUtilities; using namespace TestUtilities; @@ -38,8 +47,11 @@ class IoTests : public TestFixture { CPPUNIT_TEST(testPathUtilities); CPPUNIT_TEST(testIniFile); CPPUNIT_TEST(testCopy); - CPPUNIT_TEST(testMisc); + CPPUNIT_TEST(testReadFile); CPPUNIT_TEST(testAnsiEscapeCodes); +#ifdef CPP_UTILITIES_USE_NATIVE_FILE_BUFFER + CPPUNIT_TEST(testNativeFileStream); +#endif CPPUNIT_TEST_SUITE_END(); public: @@ -53,8 +65,9 @@ public: void testPathUtilities(); void testIniFile(); void testCopy(); - void testMisc(); + void testReadFile(); void testAnsiEscapeCodes(); + void testNativeFileStream(); }; 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")); CPPUNIT_ASSERT_EQUAL("# file for testing INI parser\n" "key0=value 0\n" @@ -401,12 +415,19 @@ void IoTests::testMisc() "#key5=value 5\n" "key6=value 6\n"s, readFile(iniFilePath)); + + // fail by exceeding max size try { readFile(iniFilePath, 10); CPPUNIT_FAIL("no exception"); } catch (...) { 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() @@ -441,3 +462,89 @@ void IoTests::testAnsiEscapeCodes() ss2 << EscapeCodes::Phrases::Info << "some info" << EscapeCodes::Phrases::End; 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(fileStream.get()), 'f'); + fileStream.seekg(0, ios_base::end); + CPPUNIT_ASSERT_EQUAL(fileStream.tellg(), static_cast(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(fileStream.get()), 'f'); + fileStream.seekg(0, ios_base::end); + CPPUNIT_ASSERT_EQUAL(fileStream.tellg(), static_cast(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