printf: Add support for glibc's %m format

glibc's printf family of functions support '%m' to emit the error string
that corresponds to the current value of errno. It has no corresponding
argument.

In order to take advantage of safe_strerror, PrintfFormatter::format calls
format_system_error, which has been modified to not emit the separator if
the message prefix is empty.

'm' needs to be added to the list of special characters in format-test.cc's
check_unknown_types since it would otherwise always be valid in the format
string.

The AppVeyor mingw test shows that errno is being reset to zero during the
processing of '%m', so it needs to be explicitly preserved. This doesn't
seem to happen on Linux, MacOS, or Windows with MSVC but the preservation
code does no harm.
This commit is contained in:
Mike Crowe 2017-07-18 18:19:03 +01:00
parent 589ccc1675
commit 0d46c7fb36
5 changed files with 52 additions and 3 deletions

View File

@ -162,7 +162,9 @@ The following functions use `printf format string syntax
<http://pubs.opengroup.org/onlinepubs/009695399/functions/fprintf.html>`_ with
the POSIX extension for positional arguments. Unlike their standard
counterparts, the ``fmt`` functions are type-safe and throw an exception if an
argument type doesn't match its format specification.
argument type doesn't match its format specification. They also support the
glibc '%m' extension to output the string corresponding to the error code
in errno.
.. doxygenfunction:: printf(CStringRef, ArgList)

View File

@ -385,7 +385,10 @@ FMT_FUNC void format_system_error(
char *system_message = &buffer[0];
int result = safe_strerror(error_code, system_message, buffer.size());
if (result == 0) {
out << message << ": " << system_message;
if (message.size())
out << message << ": " << system_message;
else
out << system_message;
return;
}
if (result != ERANGE)

View File

@ -12,6 +12,7 @@
#include <algorithm> // std::fill_n
#include <limits> // std::numeric_limits
#include <cerrno> // errno
#include "ostream.h"
@ -197,6 +198,18 @@ class WidthHandler : public ArgVisitor<WidthHandler, unsigned> {
return static_cast<unsigned>(width);
}
};
class ErrnoPreserver {
const int saved_errno_;
public:
ErrnoPreserver() : saved_errno_(errno) {}
~ErrnoPreserver() {
errno = saved_errno_;
}
int get() const {
return saved_errno_;
}
};
} // namespace internal
/**
@ -431,6 +444,21 @@ void PrintfFormatter<Char, AF>::format(BasicCStringRef<Char> format_str) {
// Parse argument index, flags and width.
unsigned arg_index = parse_header(s, spec);
if (*s == 'm') {
// Something in here is changing errno in the mingw build. We
// ought to preserve it anyway, even if an exception is thrown.
internal::ErrnoPreserver errno_preserver;
// This dance is necessary because this function is templated on
// the character type but format_system_error is not.
MemoryWriter char_writer;
format_system_error(char_writer, errno_preserver.get(), "");
AF(writer_, spec).visit_cstring(char_writer.c_str());
start = ++s;
continue;
}
// Parse precision.
if (*s == '.') {
++s;

View File

@ -1105,7 +1105,7 @@ template <typename T>
void check_unknown_types(
const T &value, const char *types, const char *type_name) {
char format_str[BUFFER_SIZE], message[BUFFER_SIZE];
const char *special = ".0123456789}";
const char *special = ".0123456789}m";
for (int i = CHAR_MIN; i <= CHAR_MAX; ++i) {
char c = static_cast<char>(i);
if (std::strchr(types, c) || std::strchr(special, c) || !c) continue;

View File

@ -51,6 +51,10 @@ std::string make_positional(fmt::StringRef format) {
<< "format: " << format; \
EXPECT_EQ(expected_output, fmt::sprintf(make_positional(format), arg))
#define EXPECT_PRINTF_NO_ARG(expected_output, format) \
EXPECT_EQ(expected_output, fmt::sprintf(format)) \
<< "format: " << format;
TEST(PrintfTest, NoArgs) {
EXPECT_EQ("test", fmt::sprintf("test"));
}
@ -472,6 +476,18 @@ TEST(PrintfTest, Enum) {
EXPECT_PRINTF("42", "%d", A);
}
TEST(PrintfTest, ErrorMessage) {
errno = ENOENT;
EXPECT_PRINTF_NO_ARG("No such file or directory", "%m");
EXPECT_PRINTF_NO_ARG("No such file or directory ", "%-30m");
EXPECT_PRINTF_NO_ARG(" No such file or directory", "%30m");
EXPECT_PRINTF_NO_ARG(L"No such file or directory", L"%m");
EXPECT_PRINTF_NO_ARG(L"No such file or directory ", L"%-30m");
EXPECT_PRINTF_NO_ARG(L" No such file or directory", L"%30m");
}
#if FMT_USE_FILE_DESCRIPTORS
TEST(PrintfTest, Examples) {
const char *weekday = "Thursday";