diff options
author | tschmelcher@chromium.org <tschmelcher@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-10-09 23:52:20 +0000 |
---|---|---|
committer | tschmelcher@chromium.org <tschmelcher@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-10-09 23:52:20 +0000 |
commit | d8617a6ad0d531e8ad63298f7cd4a091b78aa43e (patch) | |
tree | e60489d36fcbdf6f580e7c4cd715f2a085d5b56b /base | |
parent | a36804546751df937333345b7a27b4ef8d60b67d (diff) | |
download | chromium_src-d8617a6ad0d531e8ad63298f7cd4a091b78aa43e.zip chromium_src-d8617a6ad0d531e8ad63298f7cd4a091b78aa43e.tar.gz chromium_src-d8617a6ad0d531e8ad63298f7cd4a091b78aa43e.tar.bz2 |
Add logging macros that automatically append the last system error in string form.
Also add thread-safe, portable variants for strerror() and strerror_r() on POSIX so that existing error logging code that calls strerror() for something other than LOG, LOG_IF, or CHECK can be changed to use safe versions too. After this CL I will eliminate all unsafe uses of strerror() in our code.
TEST=Linux: tested PLOG and DPLOG with both a valid error and invalid error on a dbg build with both the default strerror_r implementation (GNU) and the other one (POSIX) via some throw-away macro evilness, and also tested the default strerror_r again on an opt build to verify DPLOG is a no-op; Windows: tested PLOG and DPLOG with both a valid error and invalid error on a dbg build; also tested LOG_GETLASTERROR_MODULE with winhttp and ERROR_WINHTTP_CANNOT_CONNECT and verified that it prints the correct system message and that it doesn't with PLOG; also tested LOG_GETLASTERROR_MODULE with a bogus module name and verified that it prints an error that it can't find the module, and the original error; Mac: none (implicitly tested via the Linux POSIX tests); trybots for Win, Mac, and Linux 32-bit; built locally for Linux 32-bit and 64-bit and tested base_unittests and also running Chromium itself; wrote the upcoming CL that switches strerror() calls to use PLOG and verified that it builds and works for both Linux 32-bit and Linux 64-bit; lint
BUG=none
Review URL: http://codereview.chromium.org/265052
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@28632 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'base')
-rw-r--r-- | base/base.gyp | 2 | ||||
-rw-r--r-- | base/logging.cc | 92 | ||||
-rw-r--r-- | base/logging.h | 178 | ||||
-rw-r--r-- | base/safe_strerror_posix.cc | 107 | ||||
-rw-r--r-- | base/safe_strerror_posix.h | 36 |
5 files changed, 407 insertions, 8 deletions
diff --git a/base/base.gyp b/base/base.gyp index 09dca2b..61f5e3c 100644 --- a/base/base.gyp +++ b/base/base.gyp @@ -250,6 +250,8 @@ 'registry.h', 'resource_util.cc', 'resource_util.h', + 'safe_strerror_posix.cc', + 'safe_strerror_posix.h', 'scoped_bstr_win.cc', 'scoped_bstr_win.h', 'scoped_cftyperef.h', diff --git a/base/logging.cc b/base/logging.cc index 7981310..b59e3fb 100644 --- a/base/logging.cc +++ b/base/logging.cc @@ -19,6 +19,7 @@ typedef HANDLE MutexHandle; #endif #if defined(OS_POSIX) +#include <errno.h> #include <stdlib.h> #include <stdio.h> #include <string.h> @@ -37,6 +38,9 @@ typedef pthread_mutex_t* MutexHandle; #include "base/command_line.h" #include "base/debug_util.h" #include "base/lock_impl.h" +#if defined(OS_POSIX) +#include "base/safe_strerror_posix.h" +#endif #include "base/string_piece.h" #include "base/string_util.h" #include "base/utf_string_conversions.h" @@ -567,6 +571,94 @@ LogMessage::~LogMessage() { } } +#if defined(OS_WIN) +// This has already been defined in the header, but defining it again as DWORD +// ensures that the type used in the header is equivalent to DWORD. If not, +// the redefinition is a compile error. +typedef DWORD SystemErrorCode; +#endif + +SystemErrorCode GetLastSystemErrorCode() { +#if defined(OS_WIN) + return ::GetLastError(); +#elif defined(OS_POSIX) + return errno; +#else +#error Not implemented +#endif +} + +#if defined(OS_WIN) +Win32ErrorLogMessage::Win32ErrorLogMessage(const char* file, + int line, + LogSeverity severity, + SystemErrorCode err, + const char* module) + : err_(err), + module_(module), + log_message_(file, line, severity) { +} + +Win32ErrorLogMessage::Win32ErrorLogMessage(const char* file, + int line, + LogSeverity severity, + SystemErrorCode err) + : err_(err), + module_(NULL), + log_message_(file, line, severity) { +} + +Win32ErrorLogMessage::~Win32ErrorLogMessage() { + const int error_message_buffer_size = 256; + char msgbuf[error_message_buffer_size]; + DWORD flags = FORMAT_MESSAGE_FROM_SYSTEM; + HMODULE hmod; + if (module_) { + hmod = GetModuleHandleA(module_); + if (hmod) { + flags |= FORMAT_MESSAGE_FROM_HMODULE; + } else { + // This makes a nested Win32ErrorLogMessage. It will have module_ of NULL + // so it will not call GetModuleHandle, so recursive errors are + // impossible. + DPLOG(WARNING) << "Couldn't open module " << module_ + << " for error message query"; + } + } else { + hmod = NULL; + } + DWORD len = FormatMessageA(flags, + hmod, + err_, + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), + msgbuf, + sizeof(msgbuf) / sizeof(msgbuf[0]), + NULL); + if (len) { + while ((len > 0) && + isspace(static_cast<unsigned char>(msgbuf[len - 1]))) { + msgbuf[--len] = 0; + } + stream() << ": " << msgbuf; + } else { + stream() << ": Error " << GetLastError() << " while retrieving error " + << err_; + } +} +#elif defined(OS_POSIX) +ErrnoLogMessage::ErrnoLogMessage(const char* file, + int line, + LogSeverity severity, + SystemErrorCode err) + : err_(err), + log_message_(file, line, severity) { +} + +ErrnoLogMessage::~ErrnoLogMessage() { + stream() << ": " << safe_strerror(err_); +} +#endif // OS_WIN + void CloseLogFile() { if (!log_file) return; diff --git a/base/logging.h b/base/logging.h index a1d439c..de8de71 100644 --- a/base/logging.h +++ b/base/logging.h @@ -75,6 +75,18 @@ // // We also override the standard 'assert' to use 'DLOG_ASSERT'. // +// Lastly, there is: +// +// PLOG(ERROR) << "Couldn't do foo"; +// DPLOG(ERROR) << "Couldn't do foo"; +// PLOG_IF(ERROR, cond) << "Couldn't do foo"; +// DPLOG_IF(ERROR, cond) << "Couldn't do foo"; +// PCHECK(condition) << "Couldn't do foo"; +// DPCHECK(condition) << "Couldn't do foo"; +// +// which append the last system error to the message in string form (taken from +// GetLastError() on Windows and errno on POSIX). +// // The supported severity levels for macros that allow you to specify one // are (in increasing order of severity) INFO, WARNING, ERROR, ERROR_REPORT, // and FATAL. @@ -192,18 +204,33 @@ const LogSeverity LOG_DFATAL_LEVEL = LOG_FATAL; // A few definitions of macros that don't generate much code. These are used // by LOG() and LOG_IF, etc. Since these are used all over our code, it's // better to have compact code for these operations. +#define COMPACT_GOOGLE_LOG_EX_INFO(ClassName, ...) \ + logging::ClassName(__FILE__, __LINE__, logging::LOG_INFO , ##__VA_ARGS__) +#define COMPACT_GOOGLE_LOG_EX_WARNING(ClassName, ...) \ + logging::ClassName(__FILE__, __LINE__, logging::LOG_WARNING , ##__VA_ARGS__) +#define COMPACT_GOOGLE_LOG_EX_ERROR(ClassName, ...) \ + logging::ClassName(__FILE__, __LINE__, logging::LOG_ERROR , ##__VA_ARGS__) +#define COMPACT_GOOGLE_LOG_EX_ERROR_REPORT(ClassName, ...) \ + logging::ClassName(__FILE__, __LINE__, \ + logging::LOG_ERROR_REPORT , ##__VA_ARGS__) +#define COMPACT_GOOGLE_LOG_EX_FATAL(ClassName, ...) \ + logging::ClassName(__FILE__, __LINE__, logging::LOG_FATAL , ##__VA_ARGS__) +#define COMPACT_GOOGLE_LOG_EX_DFATAL(ClassName, ...) \ + logging::ClassName(__FILE__, __LINE__, \ + logging::LOG_DFATAL_LEVEL , ##__VA_ARGS__) + #define COMPACT_GOOGLE_LOG_INFO \ - logging::LogMessage(__FILE__, __LINE__) + COMPACT_GOOGLE_LOG_EX_INFO(LogMessage) #define COMPACT_GOOGLE_LOG_WARNING \ - logging::LogMessage(__FILE__, __LINE__, logging::LOG_WARNING) + COMPACT_GOOGLE_LOG_EX_WARNING(LogMessage) #define COMPACT_GOOGLE_LOG_ERROR \ - logging::LogMessage(__FILE__, __LINE__, logging::LOG_ERROR) + COMPACT_GOOGLE_LOG_EX_ERROR(LogMessage) #define COMPACT_GOOGLE_LOG_ERROR_REPORT \ - logging::LogMessage(__FILE__, __LINE__, logging::LOG_ERROR_REPORT) + COMPACT_GOOGLE_LOG_EX_ERROR_REPORT(LogMessage) #define COMPACT_GOOGLE_LOG_FATAL \ - logging::LogMessage(__FILE__, __LINE__, logging::LOG_FATAL) + COMPACT_GOOGLE_LOG_EX_FATAL(LogMessage) #define COMPACT_GOOGLE_LOG_DFATAL \ - logging::LogMessage(__FILE__, __LINE__, logging::LOG_DFATAL_LEVEL) + COMPACT_GOOGLE_LOG_EX_DFATAL(LogMessage) // wingdi.h defines ERROR to be 0. When we call LOG(ERROR), it gets // substituted with 0, and it expands to COMPACT_GOOGLE_LOG_0. To allow us @@ -211,8 +238,9 @@ const LogSeverity LOG_DFATAL_LEVEL = LOG_FATAL; // as COMPACT_GOOGLE_LOG_ERROR, and also define ERROR the same way that // the Windows SDK does for consistency. #define ERROR 0 -#define COMPACT_GOOGLE_LOG_0 \ - logging::LogMessage(__FILE__, __LINE__, logging::LOG_ERROR) +#define COMPACT_GOOGLE_LOG_EX_0(ClassName, ...) \ + COMPACT_GOOGLE_LOG_EX_ERROR(ClassName , ##__VA_ARGS__) +#define COMPACT_GOOGLE_LOG_0 COMPACT_GOOGLE_LOG_ERROR // We use the preprocessor's merging operator, "##", so that, e.g., // LOG(INFO) becomes the token COMPACT_GOOGLE_LOG_INFO. There's some funny @@ -235,12 +263,38 @@ const LogSeverity LOG_DFATAL_LEVEL = LOG_FATAL; #define SYSLOG_ASSERT(condition) \ SYSLOG_IF(FATAL, !(condition)) << "Assert failed: " #condition ". " +#if defined(OS_WIN) +#define LOG_GETLASTERROR(severity) \ + COMPACT_GOOGLE_LOG_EX_ ## severity(Win32ErrorLogMessage, \ + ::logging::GetLastSystemErrorCode()).stream() +#define LOG_GETLASTERROR_MODULE(severity, module) \ + COMPACT_GOOGLE_LOG_EX_ ## severity(Win32ErrorLogMessage, \ + ::logging::GetLastSystemErrorCode(), module).stream() +// PLOG is the usual error logging macro for each platform. +#define PLOG(severity) LOG_GETLASTERROR(severity) +#define DPLOG(severity) DLOG_GETLASTERROR(severity) +#elif defined(OS_POSIX) +#define LOG_ERRNO(severity) \ + COMPACT_GOOGLE_LOG_EX_ ## severity(ErrnoLogMessage, \ + ::logging::GetLastSystemErrorCode()).stream() +// PLOG is the usual error logging macro for each platform. +#define PLOG(severity) LOG_ERRNO(severity) +#define DPLOG(severity) DLOG_ERRNO(severity) +// TODO(tschmelcher): Should we add OSStatus logging for Mac? +#endif + +#define PLOG_IF(severity, condition) \ + !(condition) ? (void) 0 : logging::LogMessageVoidify() & PLOG(severity) + // CHECK dies with a fatal error if condition is not true. It is *not* // controlled by NDEBUG, so the check will be executed regardless of // compilation mode. #define CHECK(condition) \ LOG_IF(FATAL, !(condition)) << "Check failed: " #condition ". " +#define PCHECK(condition) \ + PLOG_IF(FATAL, !(condition)) << "Check failed: " #condition ". " + // A container for a string pointer which can be evaluated to a bool - // true iff the pointer is NULL. struct CheckOpString { @@ -301,6 +355,20 @@ std::string* MakeCheckOpString(const int& v1, #define DLOG_ASSERT(condition) \ true ? (void) 0 : LOG_ASSERT(condition) +#if defined(OS_WIN) +#define DLOG_GETLASTERROR(severity) \ + true ? (void) 0 : logging::LogMessageVoidify() & LOG_GETLASTERROR(severity) +#define DLOG_GETLASTERROR_MODULE(severity, module) \ + true ? (void) 0 : logging::LogMessageVoidify() & \ + LOG_GETLASTERROR_MODULE(severity, module) +#elif defined(OS_POSIX) +#define DLOG_ERRNO(severity) \ + true ? (void) 0 : logging::LogMessageVoidify() & LOG_ERRNO(severity) +#endif + +#define DPLOG_IF(severity, condition) \ + true ? (void) 0 : logging::LogMessageVoidify() & PLOG(severity) + enum { DEBUG_MODE = 0 }; // This macro can be followed by a sequence of stream parameters in @@ -315,6 +383,9 @@ enum { DEBUG_MODE = 0 }; #define DCHECK(condition) \ while (false && (condition)) NDEBUG_EAT_STREAM_PARAMETERS +#define DPCHECK(condition) \ + while (false && (condition)) NDEBUG_EAT_STREAM_PARAMETERS + #define DCHECK_EQ(val1, val2) \ while (false && (val1) == (val2)) NDEBUG_EAT_STREAM_PARAMETERS @@ -354,11 +425,24 @@ enum { DEBUG_MODE = 0 }; #define DLOG_IF(severity, condition) LOG_IF(severity, condition) #define DLOG_ASSERT(condition) LOG_ASSERT(condition) +#if defined(OS_WIN) +#define DLOG_GETLASTERROR(severity) LOG_GETLASTERROR(severity) +#define DLOG_GETLASTERROR_MODULE(severity, module) \ + LOG_GETLASTERROR_MODULE(severity, module) +#elif defined(OS_POSIX) +#define DLOG_ERRNO(severity) LOG_ERRNO(severity) +#endif + +#define DPLOG_IF(severity, condition) PLOG_IF(severity, condition) + // debug-only checking. not executed in NDEBUG mode. enum { DEBUG_MODE = 1 }; #define DCHECK(condition) \ LOG_IF(FATAL, !(condition)) << "Check failed: " #condition ". " +#define DPCHECK(condition) \ + PLOG_IF(FATAL, !(condition)) << "Check failed: " #condition ". " + // Helper macro for binary operators. // Don't use this macro directly in your code, use DCHECK_EQ et al below. #define DCHECK_OP(name, op, val1, val2) \ @@ -413,6 +497,20 @@ DECLARE_DCHECK_STROP_IMPL(_stricmp, false) #define DLOG_ASSERT(condition) \ true ? (void) 0 : LOG_ASSERT(condition) +#if defined(OS_WIN) +#define DLOG_GETLASTERROR(severity) \ + true ? (void) 0 : logging::LogMessageVoidify() & LOG_GETLASTERROR(severity) +#define DLOG_GETLASTERROR_MODULE(severity, module) \ + true ? (void) 0 : logging::LogMessageVoidify() & \ + LOG_GETLASTERROR_MODULE(severity, module) +#elif defined(OS_POSIX) +#define DLOG_ERRNO(severity) \ + true ? (void) 0 : logging::LogMessageVoidify() & LOG_ERRNO(severity) +#endif + +#define DPLOG_IF(severity, condition) \ + true ? (void) 0 : logging::LogMessageVoidify() & PLOG(severity) + enum { DEBUG_MODE = 0 }; // This macro can be followed by a sequence of stream parameters in @@ -428,6 +526,10 @@ extern bool g_enable_dcheck; !logging::g_enable_dcheck ? void (0) : \ LOG_IF(ERROR_REPORT, !(condition)) << "Check failed: " #condition ". " +#define DPCHECK(condition) \ + !logging::g_enable_dcheck ? void (0) : \ + PLOG_IF(ERROR_REPORT, !(condition)) << "Check failed: " #condition ". " + // Helper macro for binary operators. // Don't use this macro directly in your code, use DCHECK_EQ et al below. #define DCHECK_OP(name, op, val1, val2) \ @@ -597,6 +699,66 @@ class LogMessageVoidify { void operator&(std::ostream&) { } }; +#if defined(OS_WIN) +typedef unsigned long SystemErrorCode; +#elif defined(OS_POSIX) +typedef int SystemErrorCode; +#endif + +// Alias for ::GetLastError() on Windows and errno on POSIX. Avoids having to +// pull in windows.h just for GetLastError() and DWORD. +SystemErrorCode GetLastSystemErrorCode(); + +#if defined(OS_WIN) +// Appends a formatted system message of the GetLastError() type. +class Win32ErrorLogMessage { + public: + Win32ErrorLogMessage(const char* file, + int line, + LogSeverity severity, + SystemErrorCode err, + const char* module); + + Win32ErrorLogMessage(const char* file, + int line, + LogSeverity severity, + SystemErrorCode err); + + std::ostream& stream() { return log_message_.stream(); } + + // Appends the error message before destructing the encapsulated class. + ~Win32ErrorLogMessage(); + + private: + SystemErrorCode err_; + // Optional name of the module defining the error. + const char* module_; + LogMessage log_message_; + + DISALLOW_COPY_AND_ASSIGN(Win32ErrorLogMessage); +}; +#elif defined(OS_POSIX) +// Appends a formatted system message of the errno type +class ErrnoLogMessage { + public: + ErrnoLogMessage(const char* file, + int line, + LogSeverity severity, + SystemErrorCode err); + + std::ostream& stream() { return log_message_.stream(); } + + // Appends the error message before destructing the encapsulated class. + ~ErrnoLogMessage(); + + private: + SystemErrorCode err_; + LogMessage log_message_; + + DISALLOW_COPY_AND_ASSIGN(ErrnoLogMessage); +}; +#endif // OS_WIN + // Closes the log file explicitly if open. // NOTE: Since the log file is opened as necessary by the action of logging // statements, there's no guarantee that it will stay closed diff --git a/base/safe_strerror_posix.cc b/base/safe_strerror_posix.cc new file mode 100644 index 0000000..008b785 --- /dev/null +++ b/base/safe_strerror_posix.cc @@ -0,0 +1,107 @@ +// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/safe_strerror_posix.h" + +#include <errno.h> +#include <string.h> + +#if defined(__GLIBC__) && defined(__GNUC__) +// GCC will complain about the unused second wrap function unless we tell it +// that we meant for them to be potentially unused, which is exactly what this +// attribute is for. +#define POSSIBLY_UNUSED __attribute__((unused)) +#else +#define POSSIBLY_UNUSED +#endif + +#if defined(__GLIBC__) +// glibc has two strerror_r functions: a historical GNU-specific one that +// returns type char *, and a POSIX.1-2001 compliant one available since 2.3.4 +// that returns int. This wraps the GNU-specific one. +static void POSSIBLY_UNUSED wrap_posix_strerror_r( + char *(*strerror_r_ptr)(int, char *, size_t), + int err, + char *buf, + size_t len) { + // GNU version. + char *rc = (*strerror_r_ptr)(err, buf, len); + if (rc != buf) { + // glibc did not use buf and returned a static string instead. Copy it + // into buf. + buf[0] = '\0'; + strncat(buf, rc, len - 1); + } + // The GNU version never fails. Unknown errors get an "unknown error" message. + // The result is always null terminated. +} +#endif // __GLIBC__ + +// Wrapper for strerror_r functions that implement the POSIX interface. POSIX +// does not define the behaviour for some of the edge cases, so we wrap it to +// guarantee that they are handled. This is compiled on all POSIX platforms, but +// it will only be used on Linux if the POSIX strerror_r implementation is +// being used (see below). +static void POSSIBLY_UNUSED wrap_posix_strerror_r( + int (*strerror_r_ptr)(int, char *, size_t), + int err, + char *buf, + size_t len) { + int old_errno = errno; + // Have to cast since otherwise we get an error if this is the GNU version + // (but in such a scenario this function is never called). Sadly we can't use + // C++-style casts because the appropriate one is reinterpret_cast but it's + // considered illegal to reinterpret_cast a type to itself, so we get an + // error in the opposite case. + int result = (*strerror_r_ptr)(err, buf, len); + if (result == 0) { + // POSIX is vague about whether the string will be terminated, although + // it indirectly implies that typically ERANGE will be returned, instead + // of truncating the string. We play it safe by always terminating the + // string explicitly. + buf[len - 1] = '\0'; + } else { + // Error. POSIX is vague about whether the return value is itself a system + // error code or something else. On Linux currently it is -1 and errno is + // set. On BSD-derived systems it is a system error and errno is unchanged. + // We try and detect which case it is so as to put as much useful info as + // we can into our message. + int strerror_error; // The error encountered in strerror + int new_errno = errno; + if (new_errno != old_errno) { + // errno was changed, so probably the return value is just -1 or something + // else that doesn't provide any info, and errno is the error. + strerror_error = new_errno; + } else { + // Either the error from strerror_r was the same as the previous value, or + // errno wasn't used. Assume the latter. + strerror_error = result; + } + // snprintf truncates and always null-terminates. + snprintf(buf, + len, + "Error %d while retrieving error %d", + strerror_error, + err); + } + errno = old_errno; +} + +void safe_strerror_r(int err, char *buf, size_t len) { + if (buf == NULL || len <= 0) { + return; + } + // If using glibc (i.e., Linux), the compiler will automatically select the + // appropriate overloaded function based on the function type of strerror_r. + // The other one will be elided from the translation unit since both are + // static. + wrap_posix_strerror_r(&strerror_r, err, buf, len); +} + +std::string safe_strerror(int err) { + const int buffer_size = 256; + char buf[buffer_size]; + safe_strerror_r(err, buf, sizeof(buf)); + return std::string(buf); +} diff --git a/base/safe_strerror_posix.h b/base/safe_strerror_posix.h new file mode 100644 index 0000000..ecf3a78 --- /dev/null +++ b/base/safe_strerror_posix.h @@ -0,0 +1,36 @@ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_SAFE_STRERROR_POSIX_H_ +#define BASE_SAFE_STRERROR_POSIX_H_ + +#include <string> + +// BEFORE using anything from this file, first look at PLOG and friends in +// logging.h and use them instead if applicable. +// +// This file declares safe, portable alternatives to the POSIX strerror() +// function. strerror() is inherently unsafe in multi-threaded apps and should +// never be used. Doing so can cause crashes. Additionally, the thread-safe +// alternative strerror_r varies in semantics across platforms. Use these +// functions instead. + +// Thread-safe strerror function with dependable semantics that never fails. +// It will write the string form of error "err" to buffer buf of length len. +// If there is an error calling the OS's strerror_r() function then a message to +// that effect will be printed into buf, truncating if necessary. The final +// result is always null-terminated. The value of errno is never changed. +// +// Use this instead of strerror_r(). +void safe_strerror_r(int err, char *buf, size_t len); + +// Calls safe_strerror_r with a buffer of suitable size and returns the result +// in a C++ string. +// +// Use this instead of strerror(). Note though that safe_strerror_r will be +// more robust in the case of heap corruption errors, since it doesn't need to +// allocate a string. +std::string safe_strerror(int err); + +#endif // BASE_SAFE_STRERROR_POSIX_H_ |