[libc-commits] [libc] f009f72 - [libc] Add printf strerror conversion (%m) (#105891)

via libc-commits libc-commits at lists.llvm.org
Thu Sep 19 10:48:11 PDT 2024


Author: Michael Jones
Date: 2024-09-19T10:48:08-07:00
New Revision: f009f72df5285acab0ebb600636653d7db72a552

URL: https://github.com/llvm/llvm-project/commit/f009f72df5285acab0ebb600636653d7db72a552
DIFF: https://github.com/llvm/llvm-project/commit/f009f72df5285acab0ebb600636653d7db72a552.diff

LOG: [libc] Add printf strerror conversion (%m) (#105891)

This patch adds the %m conversion to printf, which prints the
strerror(errno). Explanation of why is below, this patch also updates
the docs, tests, and build system to accomodate this.

The standard for syslog in posix specifies it uses the same format as
printf, but adds %m which prints the error message string for the
current value of errno. For ease of implementation, it's standard
practice for libc implementers to just add %m to printf instead of
creating a separate parser for syslog.

Added: 
    libc/src/stdio/printf_core/strerror_converter.h

Modified: 
    libc/config/baremetal/config.json
    libc/config/config.json
    libc/config/gpu/config.json
    libc/docs/configure.rst
    libc/docs/dev/printf_behavior.rst
    libc/src/__support/StringUtil/error_to_string.cpp
    libc/src/__support/StringUtil/error_to_string.h
    libc/src/__support/StringUtil/tables/linux_extension_errors.h
    libc/src/__support/StringUtil/tables/linux_platform_errors.h
    libc/src/__support/StringUtil/tables/minimal_platform_errors.h
    libc/src/__support/StringUtil/tables/posix_errors.h
    libc/src/__support/StringUtil/tables/stdc_errors.h
    libc/src/stdio/printf_core/CMakeLists.txt
    libc/src/stdio/printf_core/converter.cpp
    libc/src/stdio/printf_core/converter_atlas.h
    libc/src/stdio/printf_core/parser.h
    libc/test/src/stdio/CMakeLists.txt
    libc/test/src/stdio/sprintf_test.cpp
    utils/bazel/llvm-project-overlay/libc/BUILD.bazel

Removed: 
    


################################################################################
diff  --git a/libc/config/baremetal/config.json b/libc/config/baremetal/config.json
index f0ff268b562753..dc4b0517d938df 100644
--- a/libc/config/baremetal/config.json
+++ b/libc/config/baremetal/config.json
@@ -16,6 +16,9 @@
     },
     "LIBC_CONF_PRINTF_FLOAT_TO_STR_USE_MEGA_LONG_DOUBLE_TABLE": {
       "value": false
+    },
+    "LIBC_CONF_PRINTF_DISABLE_STRERROR": {
+      "value": true
     }
   },
   "qsort": {

diff  --git a/libc/config/config.json b/libc/config/config.json
index 7dfbb560a36db3..caeb3e89a84d8d 100644
--- a/libc/config/config.json
+++ b/libc/config/config.json
@@ -25,6 +25,10 @@
     "LIBC_CONF_PRINTF_DISABLE_FIXED_POINT": {
       "value": false,
       "doc": "Disable printing fixed point values in printf and friends."
+    },
+    "LIBC_CONF_PRINTF_DISABLE_STRERROR": {
+      "value": false,
+      "doc": "Disable handling of %m to print strerror in printf and friends."
     }
   },
   "scanf": {

diff  --git a/libc/config/gpu/config.json b/libc/config/gpu/config.json
index 954163947b8a48..d99f48ecbede1c 100644
--- a/libc/config/gpu/config.json
+++ b/libc/config/gpu/config.json
@@ -16,6 +16,9 @@
     },
     "LIBC_CONF_PRINTF_FLOAT_TO_STR_USE_MEGA_LONG_DOUBLE_TABLE": {
       "value": false
+    },
+    "LIBC_CONF_PRINTF_DISABLE_STRERROR": {
+      "value": true
     }
   },
   "scanf": {

diff  --git a/libc/docs/configure.rst b/libc/docs/configure.rst
index 86875d4c975c01..c28d3a36ada748 100644
--- a/libc/docs/configure.rst
+++ b/libc/docs/configure.rst
@@ -38,6 +38,7 @@ to learn about the defaults for your platform and target.
     - ``LIBC_CONF_PRINTF_DISABLE_FIXED_POINT``: Disable printing fixed point values in printf and friends.
     - ``LIBC_CONF_PRINTF_DISABLE_FLOAT``: Disable printing floating point values in printf and friends.
     - ``LIBC_CONF_PRINTF_DISABLE_INDEX_MODE``: Disable index mode in the printf format string.
+    - ``LIBC_CONF_PRINTF_DISABLE_STRERROR``: Disable handling of %m to print strerror in printf and friends.
     - ``LIBC_CONF_PRINTF_DISABLE_WRITE_INT``: Disable handling of %n in printf format string.
     - ``LIBC_CONF_PRINTF_FLOAT_TO_STR_USE_MEGA_LONG_DOUBLE_TABLE``: Use large table for better printf long double performance.
 * **"pthread" options**

diff  --git a/libc/docs/dev/printf_behavior.rst b/libc/docs/dev/printf_behavior.rst
index 40e07562f38ca2..f5507c4d167587 100644
--- a/libc/docs/dev/printf_behavior.rst
+++ b/libc/docs/dev/printf_behavior.rst
@@ -213,3 +213,15 @@ The %r, %R, %k, and %K fixed point number format specifiers are accepted as
 defined in ISO/IEC TR 18037 (the fixed point number extension). These are
 available when the compiler is detected as having support for fixed point
 numbers and the LIBC_COPT_PRINTF_DISABLE_FIXED_POINT flag is not set.
+
+The %m conversion will behave as specified by POSIX for syslog: It takes no
+arguments, and outputs the result of strerror(errno). Additionally, to match
+existing printf behaviors, it will behave as if it is a %s string conversion for
+the purpose of all options, except for the alt form flag. If the alt form flag
+is specified, %m will instead output a string matching the macro name of the
+value of errno (e.g. "ERANGE" for errno = ERANGE), again treating it as a string
+conversion. If there is no corresponding macro, then alt form %m will print the
+value of errno as an integer with the %d format, including all options. If
+errno = 0 and alt form is specified, the conversion will be a string conversion
+on "0" for simplicity of implementation. This matches what other libcs
+implementing this feature have done.

diff  --git a/libc/src/__support/StringUtil/error_to_string.cpp b/libc/src/__support/StringUtil/error_to_string.cpp
index e347ddae90f6e3..3b22021706bba2 100644
--- a/libc/src/__support/StringUtil/error_to_string.cpp
+++ b/libc/src/__support/StringUtil/error_to_string.cpp
@@ -45,7 +45,10 @@ constexpr size_t TOTAL_STR_LEN = total_str_len(PLATFORM_ERRORS);
 constexpr size_t ERR_ARRAY_SIZE = max_key_val(PLATFORM_ERRORS) + 1;
 
 constexpr MessageMapper<ERR_ARRAY_SIZE, TOTAL_STR_LEN>
-    error_mapper(PLATFORM_ERRORS);
+    ERROR_MAPPER(PLATFORM_ERRORS);
+
+constexpr MessageMapper<ERR_ARRAY_SIZE, TOTAL_STR_LEN>
+    ERRNO_NAME_MAPPER(PLATFORM_ERRNO_NAMES);
 
 cpp::string_view build_error_string(int err_num, cpp::span<char> buffer) {
   // if the buffer can't hold "Unknown error" + ' ' + num_str, then just
@@ -68,11 +71,15 @@ cpp::string_view get_error_string(int err_num) {
 }
 
 cpp::string_view get_error_string(int err_num, cpp::span<char> buffer) {
-  auto opt_str = internal::error_mapper.get_str(err_num);
+  auto opt_str = internal::ERROR_MAPPER.get_str(err_num);
   if (opt_str)
     return *opt_str;
   else
     return internal::build_error_string(err_num, buffer);
 }
 
+cpp::optional<cpp::string_view> try_get_errno_name(int err_num) {
+  return internal::ERRNO_NAME_MAPPER.get_str(err_num);
+}
+
 } // namespace LIBC_NAMESPACE_DECL

diff  --git a/libc/src/__support/StringUtil/error_to_string.h b/libc/src/__support/StringUtil/error_to_string.h
index ff5868457f8c91..dfbd03f0703b3b 100644
--- a/libc/src/__support/StringUtil/error_to_string.h
+++ b/libc/src/__support/StringUtil/error_to_string.h
@@ -9,6 +9,7 @@
 #ifndef LLVM_LIBC_SRC___SUPPORT_STRINGUTIL_ERROR_TO_STRING_H
 #define LLVM_LIBC_SRC___SUPPORT_STRINGUTIL_ERROR_TO_STRING_H
 
+#include "src/__support/CPP/optional.h"
 #include "src/__support/CPP/span.h"
 #include "src/__support/CPP/string_view.h"
 #include "src/__support/macros/config.h"
@@ -19,6 +20,8 @@ cpp::string_view get_error_string(int err_num);
 
 cpp::string_view get_error_string(int err_num, cpp::span<char> buffer);
 
+cpp::optional<cpp::string_view> try_get_errno_name(int err_num);
+
 } // namespace LIBC_NAMESPACE_DECL
 
 #endif // LLVM_LIBC_SRC___SUPPORT_STRINGUTIL_ERROR_TO_STRING_H

diff  --git a/libc/src/__support/StringUtil/tables/linux_extension_errors.h b/libc/src/__support/StringUtil/tables/linux_extension_errors.h
index 9c60a07220491e..f6e8dea154d3be 100644
--- a/libc/src/__support/StringUtil/tables/linux_extension_errors.h
+++ b/libc/src/__support/StringUtil/tables/linux_extension_errors.h
@@ -71,6 +71,61 @@ constexpr MsgTable<52> LINUX_ERRORS = {
     MsgMapping(EHWPOISON, "Memory page has hardware error"),
 };
 
+constexpr MsgTable<52> LINUX_ERRNO_NAMES = {
+    MsgMapping(ENOTBLK, "ENOTBLK"),
+    MsgMapping(ECHRNG, "ECHRNG"),
+    MsgMapping(EL2NSYNC, "EL2NSYNC"),
+    MsgMapping(EL3HLT, "EL3HLT"),
+    MsgMapping(EL3RST, "EL3RST"),
+    MsgMapping(ELNRNG, "ELNRNG"),
+    MsgMapping(EUNATCH, "EUNATCH"),
+    MsgMapping(ENOCSI, "ENOCSI"),
+    MsgMapping(EL2HLT, "EL2HLT"),
+    MsgMapping(EBADE, "EBADE"),
+    MsgMapping(EBADR, "EBADR"),
+    MsgMapping(EXFULL, "EXFULL"),
+    MsgMapping(ENOANO, "ENOANO"),
+    MsgMapping(EBADRQC, "EBADRQC"),
+    MsgMapping(EBADSLT, "EBADSLT"),
+    MsgMapping(EBFONT, "EBFONT"),
+    MsgMapping(ENONET, "ENONET"),
+    MsgMapping(ENOPKG, "ENOPKG"),
+    MsgMapping(EREMOTE, "EREMOTE"),
+    MsgMapping(EADV, "EADV"),
+    MsgMapping(ESRMNT, "ESRMNT"),
+    MsgMapping(ECOMM, "ECOMM"),
+    MsgMapping(EDOTDOT, "EDOTDOT"),
+    MsgMapping(ENOTUNIQ, "ENOTUNIQ"),
+    MsgMapping(EBADFD, "EBADFD"),
+    MsgMapping(EREMCHG, "EREMCHG"),
+    MsgMapping(ELIBACC, "ELIBACC"),
+    MsgMapping(ELIBBAD, "ELIBBAD"),
+    MsgMapping(ELIBSCN, "ELIBSCN"),
+    MsgMapping(ELIBMAX, "ELIBMAX"),
+    MsgMapping(ELIBEXEC, "ELIBEXEC"),
+    MsgMapping(ERESTART, "ERESTART"),
+    MsgMapping(ESTRPIPE, "ESTRPIPE"),
+    MsgMapping(EUSERS, "EUSERS"),
+    MsgMapping(ESOCKTNOSUPPORT, "ESOCKTNOSUPPORT"),
+    MsgMapping(EPFNOSUPPORT, "EPFNOSUPPORT"),
+    MsgMapping(ESHUTDOWN, "ESHUTDOWN"),
+    MsgMapping(ETOOMANYREFS, "ETOOMANYREFS"),
+    MsgMapping(EHOSTDOWN, "EHOSTDOWN"),
+    MsgMapping(EUCLEAN, "EUCLEAN"),
+    MsgMapping(ENOTNAM, "ENOTNAM"),
+    MsgMapping(ENAVAIL, "ENAVAIL"),
+    MsgMapping(EISNAM, "EISNAM"),
+    MsgMapping(EREMOTEIO, "EREMOTEIO"),
+    MsgMapping(ENOMEDIUM, "ENOMEDIUM"),
+    MsgMapping(EMEDIUMTYPE, "EMEDIUMTYPE"),
+    MsgMapping(ENOKEY, "ENOKEY"),
+    MsgMapping(EKEYEXPIRED, "EKEYEXPIRED"),
+    MsgMapping(EKEYREVOKED, "EKEYREVOKED"),
+    MsgMapping(EKEYREJECTED, "EKEYREJECTED"),
+    MsgMapping(ERFKILL, "ERFKILL"),
+    MsgMapping(EHWPOISON, "EHWPOISON"),
+};
+
 } // namespace LIBC_NAMESPACE_DECL
 
 #endif // LLVM_LIBC_SRC___SUPPORT_STRINGUTIL_TABLES_LINUX_EXTENSION_ERRORS_H

diff  --git a/libc/src/__support/StringUtil/tables/linux_platform_errors.h b/libc/src/__support/StringUtil/tables/linux_platform_errors.h
index 22cc87fb3654a2..ce5ee380d6b542 100644
--- a/libc/src/__support/StringUtil/tables/linux_platform_errors.h
+++ b/libc/src/__support/StringUtil/tables/linux_platform_errors.h
@@ -19,6 +19,9 @@ namespace LIBC_NAMESPACE_DECL {
 LIBC_INLINE_VAR constexpr auto PLATFORM_ERRORS =
     STDC_ERRORS + POSIX_ERRORS + LINUX_ERRORS;
 
+LIBC_INLINE_VAR constexpr auto PLATFORM_ERRNO_NAMES =
+    STDC_ERRNO_NAMES + POSIX_ERRNO_NAMES + LINUX_ERRNO_NAMES;
+
 } // namespace LIBC_NAMESPACE_DECL
 
 #endif // LLVM_LIBC_SRC___SUPPORT_STRINGUTIL_TABLES_LINUX_PLATFORM_ERRORS_H

diff  --git a/libc/src/__support/StringUtil/tables/minimal_platform_errors.h b/libc/src/__support/StringUtil/tables/minimal_platform_errors.h
index baaa07bc8c998f..a9dcb365ca113a 100644
--- a/libc/src/__support/StringUtil/tables/minimal_platform_errors.h
+++ b/libc/src/__support/StringUtil/tables/minimal_platform_errors.h
@@ -16,6 +16,8 @@ namespace LIBC_NAMESPACE_DECL {
 
 LIBC_INLINE_VAR constexpr auto PLATFORM_ERRORS = STDC_ERRORS;
 
+LIBC_INLINE_VAR constexpr auto PLATFORM_ERRNO_NAMES = STDC_ERRNO_NAMES;
+
 } // namespace LIBC_NAMESPACE_DECL
 
 #endif // LLVM_LIBC_SRC___SUPPORT_STRINGUTIL_TABLES_MINIMAL_PLATFORM_ERRORS_H

diff  --git a/libc/src/__support/StringUtil/tables/posix_errors.h b/libc/src/__support/StringUtil/tables/posix_errors.h
index aae00d11ad5561..5294f85eb8d24e 100644
--- a/libc/src/__support/StringUtil/tables/posix_errors.h
+++ b/libc/src/__support/StringUtil/tables/posix_errors.h
@@ -95,6 +95,85 @@ LIBC_INLINE_VAR constexpr MsgTable<76> POSIX_ERRORS = {
     MsgMapping(ENOTRECOVERABLE, "State not recoverable"),
 };
 
+LIBC_INLINE_VAR constexpr MsgTable<76> POSIX_ERRNO_NAMES = {
+    MsgMapping(EPERM, "EPERM"),
+    MsgMapping(ENOENT, "ENOENT"),
+    MsgMapping(ESRCH, "ESRCH"),
+    MsgMapping(EINTR, "EINTR"),
+    MsgMapping(EIO, "EIO"),
+    MsgMapping(ENXIO, "ENXIO"),
+    MsgMapping(E2BIG, "E2BIG"),
+    MsgMapping(ENOEXEC, "ENOEXEC"),
+    MsgMapping(EBADF, "EBADF"),
+    MsgMapping(ECHILD, "ECHILD"),
+    MsgMapping(EAGAIN, "EAGAIN"),
+    MsgMapping(ENOMEM, "ENOMEM"),
+    MsgMapping(EACCES, "EACCES"),
+    MsgMapping(EFAULT, "EFAULT"),
+    MsgMapping(EBUSY, "EBUSY"),
+    MsgMapping(EEXIST, "EEXIST"),
+    MsgMapping(EXDEV, "EXDEV"),
+    MsgMapping(ENODEV, "ENODEV"),
+    MsgMapping(ENOTDIR, "ENOTDIR"),
+    MsgMapping(EISDIR, "EISDIR"),
+    MsgMapping(EINVAL, "EINVAL"),
+    MsgMapping(ENFILE, "ENFILE"),
+    MsgMapping(EMFILE, "EMFILE"),
+    MsgMapping(ENOTTY, "ENOTTY"),
+    MsgMapping(ETXTBSY, "ETXTBSY"),
+    MsgMapping(EFBIG, "EFBIG"),
+    MsgMapping(ENOSPC, "ENOSPC"),
+    MsgMapping(ESPIPE, "ESPIPE"),
+    MsgMapping(EROFS, "EROFS"),
+    MsgMapping(EMLINK, "EMLINK"),
+    MsgMapping(EPIPE, "EPIPE"),
+    MsgMapping(EDEADLK, "EDEADLK"),
+    MsgMapping(ENAMETOOLONG, "ENAMETOOLONG"),
+    MsgMapping(ENOLCK, "ENOLCK"),
+    MsgMapping(ENOSYS, "ENOSYS"),
+    MsgMapping(ENOTEMPTY, "ENOTEMPTY"),
+    MsgMapping(ELOOP, "ELOOP"),
+    MsgMapping(ENOMSG, "ENOMSG"),
+    MsgMapping(EIDRM, "EIDRM"),
+    MsgMapping(ENOSTR, "ENOSTR"),
+    MsgMapping(ENODATA, "ENODATA"),
+    MsgMapping(ETIME, "ETIME"),
+    MsgMapping(ENOSR, "ENOSR"),
+    MsgMapping(ENOLINK, "ENOLINK"),
+    MsgMapping(EPROTO, "EPROTO"),
+    MsgMapping(EMULTIHOP, "EMULTIHOP"),
+    MsgMapping(EBADMSG, "EBADMSG"),
+    MsgMapping(EOVERFLOW, "EOVERFLOW"),
+    MsgMapping(ENOTSOCK, "ENOTSOCK"),
+    MsgMapping(EDESTADDRREQ, "EDESTADDRREQ"),
+    MsgMapping(EMSGSIZE, "EMSGSIZE"),
+    MsgMapping(EPROTOTYPE, "EPROTOTYPE"),
+    MsgMapping(ENOPROTOOPT, "ENOPROTOOPT"),
+    MsgMapping(EPROTONOSUPPORT, "EPROTONOSUPPORT"),
+    MsgMapping(ENOTSUP, "ENOTSUP"),
+    MsgMapping(EAFNOSUPPORT, "EAFNOSUPPORT"),
+    MsgMapping(EADDRINUSE, "EADDRINUSE"),
+    MsgMapping(EADDRNOTAVAIL, "EADDRNOTAVAIL"),
+    MsgMapping(ENETDOWN, "ENETDOWN"),
+    MsgMapping(ENETUNREACH, "ENETUNREACH"),
+    MsgMapping(ENETRESET, "ENETRESET"),
+    MsgMapping(ECONNABORTED, "ECONNABORTED"),
+    MsgMapping(ECONNRESET, "ECONNRESET"),
+    MsgMapping(ENOBUFS, "ENOBUFS"),
+    MsgMapping(EISCONN, "EISCONN"),
+    MsgMapping(ENOTCONN, "ENOTCONN"),
+    MsgMapping(ETIMEDOUT, "ETIMEDOUT"),
+    MsgMapping(ECONNREFUSED, "ECONNREFUSED"),
+    MsgMapping(EHOSTUNREACH, "EHOSTUNREACH"),
+    MsgMapping(EALREADY, "EALREADY"),
+    MsgMapping(EINPROGRESS, "EINPROGRESS"),
+    MsgMapping(ESTALE, "ESTALE"),
+    MsgMapping(EDQUOT, "EDQUOT"),
+    MsgMapping(ECANCELED, "ECANCELED"),
+    MsgMapping(EOWNERDEAD, "EOWNERDEAD"),
+    MsgMapping(ENOTRECOVERABLE, "ENOTRECOVERABLE"),
+};
+
 } // namespace LIBC_NAMESPACE_DECL
 
 #endif // LLVM_LIBC_SRC___SUPPORT_STRINGUTIL_TABLES_POSIX_ERRORS_H

diff  --git a/libc/src/__support/StringUtil/tables/stdc_errors.h b/libc/src/__support/StringUtil/tables/stdc_errors.h
index d893be63ab89a3..1f0beef82001cd 100644
--- a/libc/src/__support/StringUtil/tables/stdc_errors.h
+++ b/libc/src/__support/StringUtil/tables/stdc_errors.h
@@ -23,6 +23,13 @@ LIBC_INLINE_VAR constexpr const MsgTable<4> STDC_ERRORS = {
     MsgMapping(EILSEQ, "Invalid or incomplete multibyte or wide character"),
 };
 
+LIBC_INLINE_VAR constexpr const MsgTable<4> STDC_ERRNO_NAMES = {
+    MsgMapping(0, "0"),
+    MsgMapping(EDOM, "EDOM"),
+    MsgMapping(ERANGE, "ERANGE"),
+    MsgMapping(EILSEQ, "EILSEQ"),
+};
+
 } // namespace LIBC_NAMESPACE_DECL
 
 #endif // LLVM_LIBC_SRC___SUPPORT_STRINGUTIL_TABLES_STDC_ERRORS_H

diff  --git a/libc/src/stdio/printf_core/CMakeLists.txt b/libc/src/stdio/printf_core/CMakeLists.txt
index 2d84cb79ea3782..1095f01d71f24e 100644
--- a/libc/src/stdio/printf_core/CMakeLists.txt
+++ b/libc/src/stdio/printf_core/CMakeLists.txt
@@ -13,6 +13,9 @@ endif()
 if(LIBC_CONF_PRINTF_DISABLE_FIXED_POINT)
   list(APPEND printf_config_copts "-DLIBC_COPT_PRINTF_DISABLE_FIXED_POINT")
 endif()
+if(LIBC_CONF_PRINTF_DISABLE_STRERROR)
+  list(APPEND printf_config_copts "-DLIBC_COPT_PRINTF_DISABLE_STRERROR")
+endif()
 if(printf_config_copts)
   list(PREPEND printf_config_copts "COMPILE_OPTIONS")
 endif()
@@ -81,6 +84,7 @@ add_object_library(
     float_hex_converter.h
     float_dec_converter.h
     fixed_converter.h #TODO: Check if this should be disabled when fixed unavail
+    strerror_converter.h
   DEPENDS
     .core_structs
     .printf_config
@@ -97,6 +101,7 @@ add_object_library(
     libc.src.__support.integer_to_string
     libc.src.__support.libc_assert
     libc.src.__support.uint128
+    libc.src.__support.StringUtil.error_to_string
 )
 
 add_object_library(

diff  --git a/libc/src/stdio/printf_core/converter.cpp b/libc/src/stdio/printf_core/converter.cpp
index 37af5dc1ea026c..b1c66451f53f0f 100644
--- a/libc/src/stdio/printf_core/converter.cpp
+++ b/libc/src/stdio/printf_core/converter.cpp
@@ -11,6 +11,7 @@
 #include "src/__support/macros/config.h"
 #include "src/stdio/printf_core/core_structs.h"
 #include "src/stdio/printf_core/printf_config.h"
+#include "src/stdio/printf_core/strerror_converter.h"
 #include "src/stdio/printf_core/writer.h"
 
 // This option allows for replacing all of the conversion functions with custom
@@ -84,6 +85,10 @@ int convert(Writer *writer, const FormatSection &to_conv) {
   case 'K':
     return convert_fixed(writer, to_conv);
 #endif // LIBC_INTERNAL_PRINTF_HAS_FIXED_POINT
+#ifndef LIBC_COPT_PRINTF_DISABLE_STRERROR
+  case 'm':
+    return convert_strerror(writer, to_conv);
+#endif // LIBC_COPT_PRINTF_DISABLE_STRERROR
 #ifndef LIBC_COPT_PRINTF_DISABLE_WRITE_INT
   case 'n':
     return convert_write_int(writer, to_conv);

diff  --git a/libc/src/stdio/printf_core/converter_atlas.h b/libc/src/stdio/printf_core/converter_atlas.h
index 2189ed11a551ea..18cfe1e717cbea 100644
--- a/libc/src/stdio/printf_core/converter_atlas.h
+++ b/libc/src/stdio/printf_core/converter_atlas.h
@@ -43,4 +43,9 @@
 // defines convert_pointer
 #include "src/stdio/printf_core/ptr_converter.h"
 
+#ifndef LIBC_COPT_PRINTF_DISABLE_STRERROR
+// defines convert_strerror
+#include "src/stdio/printf_core/strerror_converter.h"
+#endif // LIBC_COPT_PRINTF_DISABLE_STRERROR
+
 #endif // LLVM_LIBC_SRC_STDIO_PRINTF_CORE_CONVERTER_ATLAS_H

diff  --git a/libc/src/stdio/printf_core/parser.h b/libc/src/stdio/printf_core/parser.h
index 1084cdc4349da5..e2cb734b5be715 100644
--- a/libc/src/stdio/printf_core/parser.h
+++ b/libc/src/stdio/printf_core/parser.h
@@ -24,6 +24,9 @@
 #ifdef LIBC_INTERNAL_PRINTF_HAS_FIXED_POINT
 #include "src/__support/fixed_point/fx_rep.h"
 #endif // LIBC_INTERNAL_PRINTF_HAS_FIXED_POINT
+#ifndef LIBC_COPT_PRINTF_DISABLE_STRERROR
+#include "src/errno/libc_errno.h"
+#endif // LIBC_COPT_PRINTF_DISABLE_STRERROR
 
 namespace LIBC_NAMESPACE_DECL {
 namespace printf_core {
@@ -258,9 +261,16 @@ template <typename ArgProvider> class Parser {
         }
         break;
 #endif // LIBC_INTERNAL_PRINTF_HAS_FIXED_POINT
+#ifndef LIBC_COPT_PRINTF_DISABLE_STRERROR
+      case ('m'):
+        // %m is an odd conversion in that it doesn't consume an argument, it
+        // just takes the current value of errno as its argument.
+        section.conv_val_raw = libc_errno;
+        break;
+#endif // LIBC_COPT_PRINTF_DISABLE_STRERROR
 #ifndef LIBC_COPT_PRINTF_DISABLE_WRITE_INT
-      case ('n'):
-#endif // LIBC_COPT_PRINTF_DISABLE_WRITE_INT
+      case ('n'): // Intentional fallthrough
+#endif            // LIBC_COPT_PRINTF_DISABLE_WRITE_INT
       case ('p'):
         WRITE_ARG_VAL_SIMPLEST(section.conv_val_ptr, void *, conv_index);
         break;

diff  --git a/libc/src/stdio/printf_core/strerror_converter.h b/libc/src/stdio/printf_core/strerror_converter.h
new file mode 100644
index 00000000000000..2902fd37c31ae3
--- /dev/null
+++ b/libc/src/stdio/printf_core/strerror_converter.h
@@ -0,0 +1,74 @@
+//===-- Strerror Converter for printf ---------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef LLVM_LIBC_SRC_STDIO_PRINTF_CORE_STRERROR_CONVERTER_H
+#define LLVM_LIBC_SRC_STDIO_PRINTF_CORE_STRERROR_CONVERTER_H
+
+#include "src/__support/StringUtil/error_to_string.h"
+#include "src/__support/macros/config.h"
+#include "src/stdio/printf_core/core_structs.h"
+#include "src/stdio/printf_core/int_converter.h"
+#include "src/stdio/printf_core/string_converter.h"
+#include "src/stdio/printf_core/writer.h"
+
+namespace LIBC_NAMESPACE_DECL {
+namespace printf_core {
+
+LIBC_INLINE int convert_strerror(Writer *writer, const FormatSection &to_conv) {
+  FormatSection new_conv = to_conv;
+  const int error_num = static_cast<int>(to_conv.conv_val_raw);
+
+  // The %m conversion takes no arguments passes the result of strerror(errno)
+  // to a string conversion (including all options). If the alternate form flag
+  // is set, then if errno is a valid error number the string of the errno macro
+  // is passed to a string conversion, else the integer value of errno is passed
+  // to an integer conversion.
+
+  // It's assumed that errno is passed in to_conv.conv_val_raw.
+
+  // normal form
+  if ((to_conv.flags & FormatFlags::ALTERNATE_FORM) == 0) {
+    char strerror_buff[64];
+    auto strerror_result = get_error_string(error_num, strerror_buff);
+    new_conv.conv_val_ptr =
+        reinterpret_cast<void *>(const_cast<char *>(strerror_result.data()));
+    new_conv.conv_name = 's';
+    return convert_string(writer, new_conv);
+  } else {
+    // alt form
+
+    // The handling of errno = 0 is in alt form weird. The rule for %m in alt
+    // form is "named macros print their name, else print errno as int." There
+    // isn't a specific name for errno = 0, but it does have an explicit meaning
+    // (success). Due to the way the string mappings work, it's easiest to just
+    // say that 0 is a valid macro with a string of "0". This works fine for
+    // most cases, but for precision and the int flags it changes the behavior.
+    // Given that this behavior is so incredibly deep in the weeds I doubt
+    // anyone would notice, I'm going to leave it as the simplest to implement
+    // (0 maps to "0"), which also happens to match what other libc
+    // implementations have done.
+
+    auto errno_name = try_get_errno_name(error_num);
+    // if there's a name available, use it.
+    if (errno_name) {
+      new_conv.conv_val_ptr =
+          reinterpret_cast<void *>(const_cast<char *>(errno_name->data()));
+      new_conv.conv_name = 's';
+      return convert_string(writer, new_conv);
+    } else {
+      // else do an int conversion
+      new_conv.conv_name = 'd';
+      return convert_int(writer, new_conv);
+    }
+  }
+}
+
+} // namespace printf_core
+} // namespace LIBC_NAMESPACE_DECL
+
+#endif // LLVM_LIBC_SRC_STDIO_PRINTF_CORE_STRERROR_CONVERTER_H

diff  --git a/libc/test/src/stdio/CMakeLists.txt b/libc/test/src/stdio/CMakeLists.txt
index 00379ba1877e82..e970e4cf014052 100644
--- a/libc/test/src/stdio/CMakeLists.txt
+++ b/libc/test/src/stdio/CMakeLists.txt
@@ -125,6 +125,9 @@ endif()
 if(LIBC_CONF_PRINTF_DISABLE_FIXED_POINT)
   list(APPEND sprintf_test_copts "-DLIBC_COPT_PRINTF_DISABLE_FIXED_POINT")
 endif()
+if(LIBC_CONF_PRINTF_DISABLE_STRERROR)
+  list(APPEND sprintf_test_copts "-DLIBC_COPT_PRINTF_DISABLE_STRERROR")
+endif()
 
 add_fp_unittest(
   sprintf_test

diff  --git a/libc/test/src/stdio/sprintf_test.cpp b/libc/test/src/stdio/sprintf_test.cpp
index 45d35ad3930da3..54076eb64f205c 100644
--- a/libc/test/src/stdio/sprintf_test.cpp
+++ b/libc/test/src/stdio/sprintf_test.cpp
@@ -10,6 +10,7 @@
 #include "src/stdio/sprintf.h"
 
 #include "src/__support/FPUtil/FPBits.h"
+#include "src/errno/libc_errno.h"
 #include "test/UnitTest/RoundingModeUtils.h"
 #include "test/UnitTest/Test.h"
 #include <inttypes.h>
@@ -3500,6 +3501,187 @@ TEST_F(LlvmLibcSPrintfTest, FixedConv) {
 #endif // defined(LIBC_COMPILER_HAS_FIXED_POINT) &&
        // !defined(LIBC_COPT_PRINTF_DISABLE_FIXED_POINT)
 
+#ifndef LIBC_COPT_PRINTF_DISABLE_STRERROR
+TEST_F(LlvmLibcSPrintfTest, StrerrorConv) {
+  LIBC_NAMESPACE::libc_errno = 0;
+  written = LIBC_NAMESPACE::sprintf(buff, "%m");
+  ASSERT_STREQ_LEN(written, buff, "Success");
+
+  LIBC_NAMESPACE::libc_errno = ERANGE;
+  written = LIBC_NAMESPACE::sprintf(buff, "%m");
+  ASSERT_STREQ_LEN(written, buff, "Numerical result out of range");
+
+  // Check that it correctly consumes no arguments.
+  LIBC_NAMESPACE::libc_errno = 0;
+  written = LIBC_NAMESPACE::sprintf(buff, "%m %d", 1);
+  ASSERT_STREQ_LEN(written, buff, "Success 1");
+
+  // Width Tests
+
+  LIBC_NAMESPACE::libc_errno = 0;
+  written = LIBC_NAMESPACE::sprintf(buff, "%10m");
+  ASSERT_STREQ_LEN(written, buff, "   Success");
+
+  LIBC_NAMESPACE::libc_errno = ERANGE;
+  written = LIBC_NAMESPACE::sprintf(buff, "%10m");
+  ASSERT_STREQ_LEN(written, buff, "Numerical result out of range");
+
+  // Precision Tests
+
+  LIBC_NAMESPACE::libc_errno = 0;
+  written = LIBC_NAMESPACE::sprintf(buff, "%.10m");
+  ASSERT_STREQ_LEN(written, buff, "Success");
+
+  LIBC_NAMESPACE::libc_errno = ERANGE;
+  written = LIBC_NAMESPACE::sprintf(buff, "%.10m");
+  ASSERT_STREQ_LEN(written, buff, "Numerical ");
+
+  // Flag Tests (Only '-' since the others only affect ints)
+
+  LIBC_NAMESPACE::libc_errno = 0;
+  written = LIBC_NAMESPACE::sprintf(buff, "%-10m");
+  ASSERT_STREQ_LEN(written, buff, "Success   ");
+
+  LIBC_NAMESPACE::libc_errno = ERANGE;
+  written = LIBC_NAMESPACE::sprintf(buff, "%-10m");
+  ASSERT_STREQ_LEN(written, buff, "Numerical result out of range");
+
+  // Alt Mode Tests
+  // Since alt mode here is effectively a completely separate conversion, it
+  // gets separate tests.
+
+  LIBC_NAMESPACE::libc_errno = 0;
+  written = LIBC_NAMESPACE::sprintf(buff, "%#m");
+  ASSERT_STREQ_LEN(written, buff, "0");
+
+  LIBC_NAMESPACE::libc_errno = ERANGE;
+  written = LIBC_NAMESPACE::sprintf(buff, "%#m");
+  ASSERT_STREQ_LEN(written, buff, "ERANGE");
+
+  LIBC_NAMESPACE::libc_errno = -9999;
+  written = LIBC_NAMESPACE::sprintf(buff, "%#m");
+  ASSERT_STREQ_LEN(written, buff, "-9999");
+
+  // Alt Mode Width
+
+  LIBC_NAMESPACE::libc_errno = 0;
+  written = LIBC_NAMESPACE::sprintf(buff, "%#10m");
+  ASSERT_STREQ_LEN(written, buff, "         0");
+
+  LIBC_NAMESPACE::libc_errno = ERANGE;
+  written = LIBC_NAMESPACE::sprintf(buff, "%#10m");
+  ASSERT_STREQ_LEN(written, buff, "    ERANGE");
+
+  LIBC_NAMESPACE::libc_errno = -9999;
+  written = LIBC_NAMESPACE::sprintf(buff, "%#10m");
+  ASSERT_STREQ_LEN(written, buff, "     -9999");
+
+  LIBC_NAMESPACE::libc_errno = ERANGE;
+  written = LIBC_NAMESPACE::sprintf(buff, "%#3m");
+  ASSERT_STREQ_LEN(written, buff, "ERANGE");
+
+  LIBC_NAMESPACE::libc_errno = -9999;
+  written = LIBC_NAMESPACE::sprintf(buff, "%#3m");
+  ASSERT_STREQ_LEN(written, buff, "-9999");
+
+  // Alt Mode Precision
+
+  LIBC_NAMESPACE::libc_errno = ERANGE;
+  written = LIBC_NAMESPACE::sprintf(buff, "%#.10m");
+  ASSERT_STREQ_LEN(written, buff, "ERANGE");
+
+  LIBC_NAMESPACE::libc_errno = -9999;
+  written = LIBC_NAMESPACE::sprintf(buff, "%#.10m");
+  ASSERT_STREQ_LEN(written, buff, "-0000009999");
+
+  LIBC_NAMESPACE::libc_errno = ERANGE;
+  written = LIBC_NAMESPACE::sprintf(buff, "%#.3m");
+  ASSERT_STREQ_LEN(written, buff, "ERA");
+
+  LIBC_NAMESPACE::libc_errno = -9999;
+  written = LIBC_NAMESPACE::sprintf(buff, "%#.3m");
+  ASSERT_STREQ_LEN(written, buff, "-9999");
+
+  // We don't test precision (or int flags) on errno = 0 because it behaves
+  // weirdly, see the docs for more information.
+  LIBC_NAMESPACE::libc_errno = 0;
+  written = LIBC_NAMESPACE::sprintf(buff, "%#.1m");
+  ASSERT_STREQ_LEN(written, buff, "0");
+
+  // Alt Mode Flags
+
+  // '-' flag
+  LIBC_NAMESPACE::libc_errno = 0;
+  written = LIBC_NAMESPACE::sprintf(buff, "%#-10m");
+  ASSERT_STREQ_LEN(written, buff, "0         ");
+
+  LIBC_NAMESPACE::libc_errno = ERANGE;
+  written = LIBC_NAMESPACE::sprintf(buff, "%#-10m");
+  ASSERT_STREQ_LEN(written, buff, "ERANGE    ");
+
+  LIBC_NAMESPACE::libc_errno = -9999;
+  written = LIBC_NAMESPACE::sprintf(buff, "%#-10m");
+  ASSERT_STREQ_LEN(written, buff, "-9999     ");
+
+  LIBC_NAMESPACE::libc_errno = ERANGE;
+  written = LIBC_NAMESPACE::sprintf(buff, "%#-3m");
+  ASSERT_STREQ_LEN(written, buff, "ERANGE");
+
+  LIBC_NAMESPACE::libc_errno = -9999;
+  written = LIBC_NAMESPACE::sprintf(buff, "%#-3m");
+  ASSERT_STREQ_LEN(written, buff, "-9999");
+
+  // '+' flag
+  LIBC_NAMESPACE::libc_errno = ERANGE;
+  written = LIBC_NAMESPACE::sprintf(buff, "%#+m");
+  ASSERT_STREQ_LEN(written, buff, "ERANGE");
+
+  LIBC_NAMESPACE::libc_errno = -9999;
+  written = LIBC_NAMESPACE::sprintf(buff, "%#+m");
+  ASSERT_STREQ_LEN(written, buff, "-9999");
+
+  // Technically 9999 could be a valid error, since the standard just says errno
+  // macros are "distinct positive values". In practice I don't expect this to
+  // come up, but I've avoided it for the other %m tests for ease of
+  // refactoring if necessary. Here it needs to be positive to test that the
+  // flags that only affect positive signed integers are properly passed along.
+  LIBC_NAMESPACE::libc_errno = 9999;
+  written = LIBC_NAMESPACE::sprintf(buff, "%#+m");
+  ASSERT_STREQ_LEN(written, buff, "+9999");
+
+  // ' ' flag
+  LIBC_NAMESPACE::libc_errno = ERANGE;
+  written = LIBC_NAMESPACE::sprintf(buff, "%# m");
+  ASSERT_STREQ_LEN(written, buff, "ERANGE");
+
+  LIBC_NAMESPACE::libc_errno = -9999;
+  written = LIBC_NAMESPACE::sprintf(buff, "%# m");
+  ASSERT_STREQ_LEN(written, buff, "-9999");
+
+  LIBC_NAMESPACE::libc_errno = 9999;
+  written = LIBC_NAMESPACE::sprintf(buff, "%# m");
+  ASSERT_STREQ_LEN(written, buff, " 9999");
+
+  // '0' flag
+
+  LIBC_NAMESPACE::libc_errno = ERANGE;
+  written = LIBC_NAMESPACE::sprintf(buff, "%#010m");
+  ASSERT_STREQ_LEN(written, buff, "    ERANGE");
+
+  LIBC_NAMESPACE::libc_errno = -9999;
+  written = LIBC_NAMESPACE::sprintf(buff, "%#010m");
+  ASSERT_STREQ_LEN(written, buff, "-000009999");
+
+  LIBC_NAMESPACE::libc_errno = ERANGE;
+  written = LIBC_NAMESPACE::sprintf(buff, "%#03m");
+  ASSERT_STREQ_LEN(written, buff, "ERANGE");
+
+  LIBC_NAMESPACE::libc_errno = -9999;
+  written = LIBC_NAMESPACE::sprintf(buff, "%#03m");
+  ASSERT_STREQ_LEN(written, buff, "-9999");
+}
+#endif // LIBC_COPT_PRINTF_DISABLE_STRERROR
+
 #ifndef LIBC_COPT_PRINTF_DISABLE_WRITE_INT
 TEST(LlvmLibcSPrintfTest, WriteIntConv) {
   char buff[64];

diff  --git a/utils/bazel/llvm-project-overlay/libc/BUILD.bazel b/utils/bazel/llvm-project-overlay/libc/BUILD.bazel
index b6ccf1a659caf4..f253f69b513354 100644
--- a/utils/bazel/llvm-project-overlay/libc/BUILD.bazel
+++ b/utils/bazel/llvm-project-overlay/libc/BUILD.bazel
@@ -3900,6 +3900,7 @@ libc_support_library(
         ":__support_str_to_integer",
         ":printf_config",
         ":printf_core_structs",
+        ":errno"
     ],
 )
 
@@ -3948,6 +3949,7 @@ libc_support_library(
         "src/stdio/printf_core/float_inf_nan_converter.h",
         "src/stdio/printf_core/int_converter.h",
         "src/stdio/printf_core/ptr_converter.h",
+        "src/stdio/printf_core/strerror_converter.h",
         "src/stdio/printf_core/string_converter.h",
         "src/stdio/printf_core/write_int_converter.h",
     ],
@@ -3963,6 +3965,7 @@ libc_support_library(
         ":__support_fputil_rounding_mode",
         ":__support_integer_to_string",
         ":__support_libc_assert",
+        ":__support_stringutil",
         ":__support_uint128",
         ":printf_config",
         ":printf_core_structs",


        


More information about the libc-commits mailing list