From 859a4975f603c33dda7c237fc0f4ed6ccbab64c9 Mon Sep 17 00:00:00 2001 From: Victor Zverovich Date: Wed, 30 Apr 2014 06:55:21 -0700 Subject: [PATCH] Throw SystemError if fwrite fails. --- format-test.cc | 14 +++- format.cc | 61 ++++++++++++++++++ format.h | 172 +++++++++++++++++++++++++++++++------------------ 3 files changed, 182 insertions(+), 65 deletions(-) diff --git a/format-test.cc b/format-test.cc index 02150728..7c62ddbb 100644 --- a/format-test.cc +++ b/format-test.cc @@ -100,7 +100,7 @@ using fmt::pad; } \ catch (expected_exception const& e) { \ gtest_caught_expected = true; \ - if (std::strcmp(message, e.what()) != 0) \ + if (std::string(message) != e.what()) \ throw; \ } \ catch (...) { \ @@ -1561,6 +1561,18 @@ TEST(FormatterTest, FileSink) { EXPECT_EQ("test", ReadFile("out")); } +TEST(FormatterTest, FileSinkWriteError) { + FILE *f = std::fopen("out", "r"); + fmt::FileSink fs(f); + int result = std::fwrite(" ", 1, 1, f); + int error_code = errno; + EXPECT_EQ(0, result); + std::string error_message = + str(Format("{}: {}") << "cannot write to file" << strerror(error_code)); + EXPECT_THROW_MSG(fs(Writer() << "test"), fmt::SystemError, error_message); + std::fclose(f); +} + // The test doesn't compile on older compilers which follow C++03 and // require an accessible copy constructor when binding a temporary to // a const reference. diff --git a/format.cc b/format.cc index 485df7de..517caba6 100644 --- a/format.cc +++ b/format.cc @@ -33,11 +33,17 @@ #include "format.h" +#include + #include #include #include #include +#ifdef _WIN32 +# include +#endif + using fmt::ULongLong; #if _MSC_VER @@ -161,6 +167,55 @@ void fmt::internal::ReportUnknownType(char code, const char *type) { << static_cast(code) << type)); } +void fmt::internal::FormatSystemErrorMessage( + fmt::Writer &out, int error_code, fmt::StringRef message) { +#ifndef _WIN32 + Array buffer; + buffer.resize(INLINE_BUFFER_SIZE); + char *system_message = 0; + for (;;) { + errno = 0; +# ifdef _GNU_SOURCE + system_message = strerror_r(error_code, &buffer[0], buffer.size()); +# else + strerror_r(error_code, system_message = &buffer[0], buffer.size()); +# endif + if (errno == 0) + break; + if (errno != ERANGE) { + // Can't get error message, report error code instead. + out << message << ": error code = " << error_code; + return; + } + buffer.resize(buffer.size() * 2); + } + out << message << ": " << system_message; +#else + class String { + private: + LPWSTR str_; + + public: + String() : str_() {} + ~String() { LocalFree(str_); } + LPWSTR *ptr() { return &str_; } + LPCWSTR c_str() const { return str_; } + }; + String system_message; + if (FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | + FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, 0, + error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + reinterpret_cast(system_message.ptr()), 0, 0)) { + UTF16ToUTF8 utf8_message; + if (!utf8_message.Convert(system_message.c_str())) { + out << message << ": " << utf8_message; + return; + } + } + // Can't get error message, report error code instead. + out << message << ": error code = " << error_code; +#endif +} // Fills the padding around the content and returns the pointer to the // content area. @@ -688,6 +743,12 @@ void fmt::BasicWriter::FormatParser::Format( writer.buffer_.append(start, s); } +void fmt::SystemErrorSink::operator()(const fmt::Writer &w) const { + Writer message; + internal::FormatSystemErrorMessage(message, error_code_, w.c_str()); + throw SystemError(message.c_str(), error_code_); +} + void fmt::ANSITerminalSink::operator()( const fmt::BasicWriter &w) const { char escape[] = "\x1b[30m"; diff --git a/format.h b/format.h index 69bfa008..20fe5e27 100644 --- a/format.h +++ b/format.h @@ -31,6 +31,7 @@ #include #include +#include #include // for std::ptrdiff_t #include #include @@ -120,11 +121,71 @@ FMT_GCC_EXTENSION typedef unsigned long long ULongLong; template class BasicWriter; +typedef BasicWriter Writer; +typedef BasicWriter WWriter; + template class BasicFormatter; struct FormatSpec; +/** + \rst + A string reference. It can be constructed from a C string, ``std::string`` + or as a result of a formatting operation. It is most useful as a parameter + type to allow passing different types of strings in a function, for example:: + + Formatter<> Format(StringRef format); + + Format("{}") << 42; + Format(std::string("{}")) << 42; + Format(Format("{{}}")) << 42; + \endrst + */ +template +class BasicStringRef { + private: + const Char *data_; + mutable std::size_t size_; + + public: + /** + Constructs a string reference object from a C string and a size. + If *size* is zero, which is the default, the size is computed with + `strlen`. + */ + BasicStringRef(const Char *s, std::size_t size = 0) : data_(s), size_(size) {} + + /** + Constructs a string reference from an `std::string` object. + */ + BasicStringRef(const std::basic_string &s) + : data_(s.c_str()), size_(s.size()) {} + + /** + Converts a string reference to an `std::string` object. + */ + operator std::basic_string() const { + return std::basic_string(data_, size()); + } + + /** + Returns the pointer to a C string. + */ + const Char *c_str() const { return data_; } + + /** + Returns the string size. + */ + std::size_t size() const { + if (size_ == 0) size_ = std::char_traits::length(data_); + return size_; + } +}; + +typedef BasicStringRef StringRef; +typedef BasicStringRef WStringRef; + namespace internal { // The number of characters to store in the Array object, representing the @@ -396,71 +457,36 @@ void FormatDecimal(Char *buffer, UInt value, unsigned num_digits) { template void FormatCustomArg( BasicWriter &w, const void *arg, const FormatSpec &spec); -} + +// Formats a system error message writing the output to out. +void FormatSystemErrorMessage(Writer &out, int error_code, StringRef message); + +} // namespace internal /** - \rst - A string reference. It can be constructed from a C string, ``std::string`` - or as a result of a formatting operation. It is most useful as a parameter - type to allow passing different types of strings in a function, for example:: - - Formatter<> Format(StringRef format); - - Format("{}") << 42; - Format(std::string("{}")) << 42; - Format(Format("{{}}")) << 42; - \endrst + A formatting error such as invalid format string. */ -template -class BasicStringRef { - private: - const Char *data_; - mutable std::size_t size_; - - public: - /** - Constructs a string reference object from a C string and a size. - If *size* is zero, which is the default, the size is computed with - `strlen`. - */ - BasicStringRef(const Char *s, std::size_t size = 0) : data_(s), size_(size) {} - - /** - Constructs a string reference from an `std::string` object. - */ - BasicStringRef(const std::basic_string &s) - : data_(s.c_str()), size_(s.size()) {} - - /** - Converts a string reference to an `std::string` object. - */ - operator std::basic_string() const { - return std::basic_string(data_, size()); - } - - /** - Returns the pointer to a C string. - */ - const Char *c_str() const { return data_; } - - /** - Returns the string size. - */ - std::size_t size() const { - if (size_ == 0) size_ = std::char_traits::length(data_); - return size_; - } -}; - -typedef BasicStringRef StringRef; -typedef BasicStringRef WStringRef; - class FormatError : public std::runtime_error { public: explicit FormatError(const std::string &message) : std::runtime_error(message) {} }; +/** + An error returned by the operating system or the language runtime, + for example a file opening error. + */ +class SystemError : public std::runtime_error { + private: + int error_code_; + + public: + SystemError(StringRef message, int error_code) + : std::runtime_error(message), error_code_(error_code) {} + + int error_code() const { return error_code_; } +}; + enum Alignment { ALIGN_DEFAULT, ALIGN_LEFT, ALIGN_RIGHT, ALIGN_CENTER, ALIGN_NUMERIC }; @@ -1197,9 +1223,6 @@ BasicFormatter BasicWriter::Format(StringRef format) { return f; } -typedef BasicWriter Writer; -typedef BasicWriter WWriter; - // The default formatting function. template void Format(BasicWriter &w, const FormatSpec &spec, const T &value) { @@ -1323,15 +1346,14 @@ template inline const Char *c_str(const BasicWriter &f) { return f.c_str(); } /** - Returns the content of the output buffer as an `std::string`. + Converts a string reference an `std::string`. */ inline std::string str(StringRef s) { return std::string(s.c_str(), s.size()); } /** - Returns a pointer to the output buffer content with terminating null - character appended. + Returns the pointer to a C string. */ inline const char *c_str(StringRef s) { return s.c_str(); @@ -1468,6 +1490,28 @@ inline Formatter Format(WStringRef format) { return f; } + +/** + A sink that gets the system error message corresponding to the error code + and throws SystemError. + */ +class SystemErrorSink { + private: + int error_code_; + + public: + explicit SystemErrorSink(int error_code) : error_code_(error_code) {} + + void operator()(const Writer &w) const; +}; + +/** Throws SystemError with a code and a formatted message. */ +inline Formatter ThrowSystemError( + int error_code, StringRef format) { + Formatter f(format, SystemErrorSink(error_code)); + return f; +} + /** A sink that writes output to a file. */ class FileSink { private: @@ -1478,8 +1522,8 @@ class FileSink { /** Writes the output to a file. */ void operator()(const BasicWriter &w) const { - // TODO: check error - std::fwrite(w.data(), w.size(), 1, file_); + if (std::fwrite(w.data(), w.size(), 1, file_) == 0) + ThrowSystemError(errno, "cannot write to file"); } };