Improve exception messages of NativeFileStream

So one gets e.g. "open failed: Permission denied" instead of
just "open failed: iostream error".
This commit is contained in:
Martchus 2020-02-14 15:15:59 +01:00
parent e5490d7c80
commit 1154ed4d1c
4 changed files with 58 additions and 12 deletions

View File

@ -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<wchar_t[]>(static_cast<size_t>(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<int>::max() - 1) ? static_cast<int>(inputBuffer.size() + 1) : -1);
ec, inputBuffer.data(), inputBuffer.size() < (numeric_limits<int>::max() - 1) ? static_cast<int>(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

View File

@ -14,6 +14,7 @@
#include <memory>
#include <sstream>
#include <string>
#include <system_error>
#include <vector>
namespace CppUtilities {
@ -49,6 +50,8 @@ CPP_UTILITIES_EXPORT StringData convertUtf8ToLatin1(const char *inputBuffer, std
#ifdef PLATFORM_WINDOWS
using WideStringData = std::pair<std::unique_ptr<wchar_t[]>, 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

View File

@ -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<StreamBuffer>(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<StreamBuffer>(handle, boost::iostreams::close_handle);
// if we wanted to open assign the descriptor as well: descriptor = _open_osfhandle(reinterpret_cast<intptr_t>(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<StreamBuffer>(descriptor, boost::iostreams::close_handle);
#endif
@ -312,9 +330,10 @@ void NativeFileStream::setData(FileBuffer data, std::ios_base::openmode openMode
*/
std::unique_ptr<wchar_t[]> 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);
}

View File

@ -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();