From 1154ed4d1c6db5e5e9b5a0d33e50600d66b4a42d Mon Sep 17 00:00:00 2001 From: Martchus Date: Fri, 14 Feb 2020 15:15:59 +0100 Subject: [PATCH] Improve exception messages of NativeFileStream So one gets e.g. "open failed: Permission denied" instead of just "open failed: iostream error". --- conversion/stringconversion.cpp | 30 +++++++++++++++++++++++++++--- conversion/stringconversion.h | 3 +++ io/nativefilestream.cpp | 31 +++++++++++++++++++++++++------ tests/iotests.cpp | 6 +++--- 4 files changed, 58 insertions(+), 12 deletions(-) diff --git a/conversion/stringconversion.cpp b/conversion/stringconversion.cpp index 8b128e7..80e81f7 100644 --- a/conversion/stringconversion.cpp +++ b/conversion/stringconversion.cpp @@ -198,18 +198,20 @@ StringData convertUtf8ToLatin1(const char *inputBuffer, std::size_t inputBufferS * - Only available under Windows. * - If \a inputBufferSize is -1, \a inputBuffer is considered null-terminated. */ -WideStringData convertMultiByteToWide(const char *inputBuffer, int inputBufferSize) +WideStringData convertMultiByteToWide(std::error_code &ec, const char *inputBuffer, int inputBufferSize) { // calculate required size WideStringData widePath; widePath.second = MultiByteToWideChar(CP_UTF8, 0, inputBuffer, inputBufferSize, nullptr, 0); if (widePath.second <= 0) { + ec = std::error_code(GetLastError(), std::system_category()); return widePath; } // do the actual conversion widePath.first = make_unique(static_cast(widePath.second)); widePath.second = MultiByteToWideChar(CP_UTF8, 0, inputBuffer, inputBufferSize, widePath.first.get(), widePath.second); if (widePath.second <= 0) { + ec = std::error_code(GetLastError(), std::system_category()); widePath.first.reset(); } return widePath; @@ -219,10 +221,32 @@ WideStringData convertMultiByteToWide(const char *inputBuffer, int inputBufferSi * \brief Converts the specified multi-byte string to a wide string using the WinAPI. * \remarks Only available under Windows. */ -WideStringData convertMultiByteToWide(const std::string &inputBuffer) +WideStringData convertMultiByteToWide(std::error_code &ec, const std::string &inputBuffer) { return convertMultiByteToWide( - inputBuffer.data(), inputBuffer.size() < (numeric_limits::max() - 1) ? static_cast(inputBuffer.size() + 1) : -1); + ec, inputBuffer.data(), inputBuffer.size() < (numeric_limits::max() - 1) ? static_cast(inputBuffer.size() + 1) : -1); +} + +/*! + * \brief Converts the specified multi-byte string to a wide string using the WinAPI. + * \remarks + * - Only available under Windows. + * - If \a inputBufferSize is -1, \a inputBuffer is considered null-terminated. + */ +WideStringData convertMultiByteToWide(const char *inputBuffer, int inputBufferSize) +{ + std::error_code ec; + return convertMultiByteToWide(ec, inputBuffer, inputBufferSize); +} + +/*! + * \brief Converts the specified multi-byte string to a wide string using the WinAPI. + * \remarks Only available under Windows. + */ +WideStringData convertMultiByteToWide(const std::string &inputBuffer) +{ + std::error_code ec; + return convertMultiByteToWide(ec, inputBuffer); } #endif diff --git a/conversion/stringconversion.h b/conversion/stringconversion.h index 23a2f11..fdbc1dc 100644 --- a/conversion/stringconversion.h +++ b/conversion/stringconversion.h @@ -14,6 +14,7 @@ #include #include #include +#include #include namespace CppUtilities { @@ -49,6 +50,8 @@ CPP_UTILITIES_EXPORT StringData convertUtf8ToLatin1(const char *inputBuffer, std #ifdef PLATFORM_WINDOWS using WideStringData = std::pair, int>; +CPP_UTILITIES_EXPORT WideStringData convertMultiByteToWide(std::error_code &ec, const char *inputBuffer, int inputBufferSize = -1); +CPP_UTILITIES_EXPORT WideStringData convertMultiByteToWide(std::error_code &ec, const std::string &inputBuffer); CPP_UTILITIES_EXPORT WideStringData convertMultiByteToWide(const char *inputBuffer, int inputBufferSize = -1); CPP_UTILITIES_EXPORT WideStringData convertMultiByteToWide(const std::string &inputBuffer); #endif diff --git a/io/nativefilestream.cpp b/io/nativefilestream.cpp index b8db653..f4096a3 100644 --- a/io/nativefilestream.cpp +++ b/io/nativefilestream.cpp @@ -2,6 +2,24 @@ #ifdef CPP_UTILITIES_USE_NATIVE_FILE_BUFFER +/*! + * \class + * So one gets e.g. "open failed: Permission denied" instead of +just "open failed: iostream error". + */ + +/*! + * \class NativeFileStream + * \brief Provides a standard IO stream instantiated using native APIs. + * + * Using this class instead of `std::fstream` has the following benefits: + * - Under Windows, the specified file path is interpreted as UTF-8 and passed to Windows' unicode API + * to support any kind of non-ASCII characters in file paths. + * - It is possible to open a file from a native file descriptor. This is for instance useful when dealing with + * Android's `content://` URLs. + * - Better error messages at least when opening a file, e.g. "Permission denied" instead of just "basic_ios::clear". + */ + #ifdef PLATFORM_WINDOWS #include "../conversion/stringconversion.h" #endif @@ -159,12 +177,12 @@ NativeFileStream::FileBuffer::FileBuffer(const string &path, ios_base::openmode #ifdef PLATFORM_WINDOWS descriptor = _wopen(widePath.get(), nativeParams.openMode, nativeParams.permissions); if (descriptor == -1) { - throw std::ios_base::failure("_wopen failed"); + throw std::ios_base::failure("_wopen failed", std::error_code(errno, std::system_category())); } #else descriptor = ::open(path.data(), nativeParams.openFlags, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); if (descriptor == -1) { - throw std::ios_base::failure("open failed"); + throw std::ios_base::failure("open failed", std::error_code(errno, std::system_category())); } #endif buffer = make_unique(descriptor, openMode); @@ -172,14 +190,14 @@ NativeFileStream::FileBuffer::FileBuffer(const string &path, ios_base::openmode #ifdef PLATFORM_WINDOWS handle = CreateFileW(widePath.get(), nativeParams.access, nativeParams.shareMode, nullptr, nativeParams.creation, FILE_ATTRIBUTE_NORMAL, nullptr); if (handle == INVALID_HANDLE_VALUE) { - throw std::ios_base::failure("CreateFileW failed"); + throw std::ios_base::failure("CreateFileW failed", std::error_code(GetLastError(), std::system_category())); } buffer = make_unique(handle, boost::iostreams::close_handle); // if we wanted to open assign the descriptor as well: descriptor = _open_osfhandle(reinterpret_cast(handle), nativeParams.flags); #else descriptor = ::open(path.data(), nativeParams.openFlags, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); if (descriptor == -1) { - throw std::ios_base::failure("open failed"); + throw std::ios_base::failure("open failed", std::error_code(errno, std::system_category())); } buffer = make_unique(descriptor, boost::iostreams::close_handle); #endif @@ -312,9 +330,10 @@ void NativeFileStream::setData(FileBuffer data, std::ios_base::openmode openMode */ std::unique_ptr NativeFileStream::makeWidePath(const std::string &path) { - auto widePath = ::CppUtilities::convertMultiByteToWide(path); + auto ec = std::error_code(); + auto widePath = ::CppUtilities::convertMultiByteToWide(ec, path); if (!widePath.first) { - throw std::ios_base::failure("Unable to convert path to UTF-16"); + throw std::ios_base::failure("converting path to UTF-16", ec); } return std::move(widePath.first); } diff --git a/tests/iotests.cpp b/tests/iotests.cpp index 0e596d3..fcc2c4d 100644 --- a/tests/iotests.cpp +++ b/tests/iotests.cpp @@ -443,12 +443,12 @@ void IoTests::testNativeFileStream() } catch (const std::ios_base::failure &failure) { #ifdef PLATFORM_WINDOWS #ifdef CPP_UTILITIES_USE_BOOST_IOSTREAMS - CPPUNIT_ASSERT_EQUAL("CreateFileW failed: iostream error"s, string(failure.what())); + TESTUTILS_ASSERT_LIKE("expected error with some message", "CreateFileW failed: .+", failure.what()); #else - CPPUNIT_ASSERT_EQUAL("_wopen failed: iostream error"s, string(failure.what())); + TESTUTILS_ASSERT_LIKE("expected error with some message", "_wopen failed: .+", failure.what()); #endif #else - CPPUNIT_ASSERT_EQUAL("open failed: iostream error"s, string(failure.what())); + TESTUTILS_ASSERT_LIKE("expected error with some message", "open failed: .+", failure.what()); #endif } fileStream.clear();