From 7f7ce3fa5d696edc76f970658669f97cf99c3e2a Mon Sep 17 00:00:00 2001 From: Bryan Call Date: Thu, 4 Jun 2026 11:29:43 -0700 Subject: [PATCH 1/2] Fix wrong errno names on FreeBSD/macOS in bwf::Errno The errno short-name lookup indexed a single hardcoded Linux-numbered table by the raw errno value, so on platforms with different numbering it printed the wrong name. A connect timeout (ETIMEDOUT=60) was logged as "ENOSTR" on FreeBSD/macOS, since 60 is ENOSTR only on Linux. Replace the single table with a per-platform table generated from each OS's own , keeping the O(1) direct index. Gaps fall through to "Unknown" and an unsupported platform is a compile error. Fixes: #13203 --- lib/swoc/src/bw_format.cc | 153 +++++++++++++++++++------------------- 1 file changed, 75 insertions(+), 78 deletions(-) diff --git a/lib/swoc/src/bw_format.cc b/lib/swoc/src/bw_format.cc index 5b87fc99090..0102d46f03c 100644 --- a/lib/swoc/src/bw_format.cc +++ b/lib/swoc/src/bw_format.cc @@ -743,83 +743,79 @@ FixedBufferWriter::operator>>(std::ostream &s) const { } namespace { -// Hand rolled, might not be totally compliant everywhere, but probably close -// enough. The long string will be locally accurate. Clang requires the double -// braces. Why, Turing only knows. -static const std::array ERRNO_SHORT_NAME = { - { - "SUCCESS", "EPERM", - "ENOENT", "ESRCH", - "EINTR", "EIO", - "ENXIO", "E2BIG ", - "ENOEXEC", "EBADF", - "ECHILD", "EAGAIN", - "ENOMEM", "EACCES", - "EFAULT", "ENOTBLK", - "EBUSY", "EEXIST", - "EXDEV", "ENODEV", - "ENOTDIR", "EISDIR", - "EINVAL", "ENFILE", - "EMFILE", "ENOTTY", - "ETXTBSY", "EFBIG", - "ENOSPC", "ESPIPE", - "EROFS", "EMLINK", - "EPIPE", "EDOM", - "ERANGE", "EDEADLK", - "ENAMETOOLONG", "ENOLCK", - "ENOSYS", "ENOTEMPTY", - "ELOOP", "EWOULDBLOCK", - "ENOMSG", "EIDRM", - "ECHRNG", "EL2NSYNC", - "EL3HLT", "EL3RST", - "ELNRNG", "EUNATCH", - "ENOCSI", "EL2HTL", - "EBADE", "EBADR", - "EXFULL", "ENOANO", - "EBADRQC", "EBADSLT", - "EDEADLOCK", "EBFONT", - "ENOSTR", "ENODATA", - "ETIME", "ENOSR", - "ENONET", "ENOPKG", - "EREMOTE", "ENOLINK", - "EADV", "ESRMNT", - "ECOMM", "EPROTO", - "EMULTIHOP", "EDOTDOT", - "EBADMSG", "EOVERFLOW", - "ENOTUNIQ", "EBADFD", - "EREMCHG", "ELIBACC", - "ELIBBAD", "ELIBSCN", - "ELIBMAX", "ELIBEXEC", - "EILSEQ", "ERESTART", - "ESTRPIPE", "EUSERS", - "ENOTSOCK", "EDESTADDRREQ", - "EMSGSIZE", "EPROTOTYPE", - "ENOPROTOOPT", "EPROTONOSUPPORT", - "ESOCKTNOSUPPORT", "EOPNOTSUPP", - "EPFNOSUPPORT", "EAFNOSUPPORT", - "EADDRINUSE", "EADDRNOTAVAIL", - "ENETDOWN", "ENETUNREACH", - "ENETRESET", "ECONNABORTED", - "ECONNRESET", "ENOBUFS", - "EISCONN", "ENOTCONN", - "ESHUTDOWN", "ETOOMANYREFS", - "ETIMEDOUT", "ECONNREFUSED", - "EHOSTDOWN", "EHOSTUNREACH", - "EALREADY", "EINPROGRESS", - "ESTALE", "EUCLEAN", - "ENOTNAM", "ENAVAIL", - "EISNAM", "EREMOTEIO", - "EDQUOT", "ENOMEDIUM", - "EMEDIUMTYPE", "ECANCELED", - "ENOKEY", "EKEYEXPIRED", - "EKEYREVOKED", "EKEYREJECTED", - "EOWNERDEAD", "ENOTRECOVERABLE", - "ERFKILL", "EHWPOISON", - } +// errno value -> symbolic name, indexed directly by the errno number (O(1)). +// The numbering differs between Linux and the BSD-derived platforms, and even +// macOS and FreeBSD disagree on a few of their own high-numbered codes, so each +// platform carries a table generated from its real . Gaps in the +// numbering are empty and fall through to "Unknown"; a platform with no table +// is a compile error. The long form (the 'l' spec, below) still uses strerror(). +// clang-format off +#if defined(__linux__) +static constexpr std::string_view ERRNO_NAMES[] = { + /* 0 */ "SUCCESS", "EPERM", "ENOENT", "ESRCH", "EINTR", "EIO", "ENXIO", "E2BIG", + /* 8 */ "ENOEXEC", "EBADF", "ECHILD", "EAGAIN", "ENOMEM", "EACCES", "EFAULT", "ENOTBLK", + /* 16 */ "EBUSY", "EEXIST", "EXDEV", "ENODEV", "ENOTDIR", "EISDIR", "EINVAL", "ENFILE", + /* 24 */ "EMFILE", "ENOTTY", "ETXTBSY", "EFBIG", "ENOSPC", "ESPIPE", "EROFS", "EMLINK", + /* 32 */ "EPIPE", "EDOM", "ERANGE", "EDEADLK", "ENAMETOOLONG", "ENOLCK", "ENOSYS", "ENOTEMPTY", + /* 40 */ "ELOOP", "", "ENOMSG", "EIDRM", "ECHRNG", "EL2NSYNC", "EL3HLT", "EL3RST", + /* 48 */ "ELNRNG", "EUNATCH", "ENOCSI", "", "EBADE", "EBADR", "EXFULL", "ENOANO", + /* 56 */ "EBADRQC", "EBADSLT", "", "EBFONT", "ENOSTR", "ENODATA", "ETIME", "ENOSR", + /* 64 */ "ENONET", "ENOPKG", "EREMOTE", "ENOLINK", "EADV", "ESRMNT", "ECOMM", "EPROTO", + /* 72 */ "EMULTIHOP", "EDOTDOT", "EBADMSG", "EOVERFLOW", "ENOTUNIQ", "EBADFD", "EREMCHG", "ELIBACC", + /* 80 */ "ELIBBAD", "ELIBSCN", "ELIBMAX", "ELIBEXEC", "EILSEQ", "ERESTART", "ESTRPIPE", "EUSERS", + /* 88 */ "ENOTSOCK", "EDESTADDRREQ", "EMSGSIZE", "EPROTOTYPE", "ENOPROTOOPT", "EPROTONOSUPPORT", "ESOCKTNOSUPPORT", "ENOTSUP", + /* 96 */ "EPFNOSUPPORT", "EAFNOSUPPORT", "EADDRINUSE", "EADDRNOTAVAIL", "ENETDOWN", "ENETUNREACH", "ENETRESET", "ECONNABORTED", + /* 104 */ "ECONNRESET", "ENOBUFS", "EISCONN", "ENOTCONN", "ESHUTDOWN", "ETOOMANYREFS", "ETIMEDOUT", "ECONNREFUSED", + /* 112 */ "EHOSTDOWN", "EHOSTUNREACH", "EALREADY", "EINPROGRESS", "ESTALE", "EUCLEAN", "ENOTNAM", "ENAVAIL", + /* 120 */ "EISNAM", "EREMOTEIO", "EDQUOT", "ENOMEDIUM", "EMEDIUMTYPE", "ECANCELED", "ENOKEY", "EKEYEXPIRED", + /* 128 */ "EKEYREVOKED", "EKEYREJECTED", "EOWNERDEAD", "ENOTRECOVERABLE", "ERFKILL", "EHWPOISON", }; -static constexpr DiscreteRange ERRNO_RANGE{0, ERRNO_SHORT_NAME.size() - 1}; -// This provides convenient safe access to the errno short name array. -auto errno_short_name = [](unsigned n) { return ERRNO_RANGE.contains(n) ? ERRNO_SHORT_NAME[n] : "Unknown"sv; }; +#elif defined(__APPLE__) +static constexpr std::string_view ERRNO_NAMES[] = { + /* 0 */ "SUCCESS", "EPERM", "ENOENT", "ESRCH", "EINTR", "EIO", "ENXIO", "E2BIG", + /* 8 */ "ENOEXEC", "EBADF", "ECHILD", "EDEADLK", "ENOMEM", "EACCES", "EFAULT", "ENOTBLK", + /* 16 */ "EBUSY", "EEXIST", "EXDEV", "ENODEV", "ENOTDIR", "EISDIR", "EINVAL", "ENFILE", + /* 24 */ "EMFILE", "ENOTTY", "ETXTBSY", "EFBIG", "ENOSPC", "ESPIPE", "EROFS", "EMLINK", + /* 32 */ "EPIPE", "EDOM", "ERANGE", "EAGAIN", "EINPROGRESS", "EALREADY", "ENOTSOCK", "EDESTADDRREQ", + /* 40 */ "EMSGSIZE", "EPROTOTYPE", "ENOPROTOOPT", "EPROTONOSUPPORT", "ESOCKTNOSUPPORT", "ENOTSUP", "EPFNOSUPPORT", "EAFNOSUPPORT", + /* 48 */ "EADDRINUSE", "EADDRNOTAVAIL", "ENETDOWN", "ENETUNREACH", "ENETRESET", "ECONNABORTED", "ECONNRESET", "ENOBUFS", + /* 56 */ "EISCONN", "ENOTCONN", "ESHUTDOWN", "ETOOMANYREFS", "ETIMEDOUT", "ECONNREFUSED", "ELOOP", "ENAMETOOLONG", + /* 64 */ "EHOSTDOWN", "EHOSTUNREACH", "ENOTEMPTY", "EPROCLIM", "EUSERS", "EDQUOT", "ESTALE", "EREMOTE", + /* 72 */ "EBADRPC", "ERPCMISMATCH", "EPROGUNAVAIL", "EPROGMISMATCH", "EPROCUNAVAIL", "ENOLCK", "ENOSYS", "EFTYPE", + /* 80 */ "EAUTH", "ENEEDAUTH", "EPWROFF", "EDEVERR", "EOVERFLOW", "EBADEXEC", "EBADARCH", "ESHLIBVERS", + /* 88 */ "EBADMACHO", "ECANCELED", "EIDRM", "ENOMSG", "EILSEQ", "", "EBADMSG", "EMULTIHOP", + /* 96 */ "ENODATA", "ENOLINK", "ENOSR", "ENOSTR", "EPROTO", "ETIME", "EOPNOTSUPP", "ENOPOLICY", + /* 104 */ "ENOTRECOVERABLE", "EOWNERDEAD", "EQFULL", "ENOTCAPABLE", +}; +#elif defined(__FreeBSD__) +static constexpr std::string_view ERRNO_NAMES[] = { + /* 0 */ "SUCCESS", "", "ENOENT", "ESRCH", "EINTR", "EIO", "ENXIO", "E2BIG", + /* 8 */ "ENOEXEC", "EBADF", "ECHILD", "EDEADLK", "ENOMEM", "EACCES", "EFAULT", "ENOTBLK", + /* 16 */ "EBUSY", "EEXIST", "EXDEV", "ENODEV", "ENOTDIR", "EISDIR", "EINVAL", "ENFILE", + /* 24 */ "EMFILE", "ENOTTY", "ETXTBSY", "EFBIG", "ENOSPC", "ESPIPE", "EROFS", "EMLINK", + /* 32 */ "EPIPE", "EDOM", "ERANGE", "EAGAIN", "EINPROGRESS", "EALREADY", "ENOTSOCK", "EDESTADDRREQ", + /* 40 */ "EMSGSIZE", "EPROTOTYPE", "ENOPROTOOPT", "EPROTONOSUPPORT", "ESOCKTNOSUPPORT", "ENOTSUP", "EPFNOSUPPORT", "EAFNOSUPPORT", + /* 48 */ "EADDRINUSE", "EADDRNOTAVAIL", "ENETDOWN", "ENETUNREACH", "ENETRESET", "ECONNABORTED", "ECONNRESET", "ENOBUFS", + /* 56 */ "EISCONN", "ENOTCONN", "ESHUTDOWN", "ETOOMANYREFS", "ETIMEDOUT", "ECONNREFUSED", "ELOOP", "ENAMETOOLONG", + /* 64 */ "EHOSTDOWN", "EHOSTUNREACH", "ENOTEMPTY", "EPROCLIM", "EUSERS", "EDQUOT", "ESTALE", "EREMOTE", + /* 72 */ "EBADRPC", "ERPCMISMATCH", "EPROGUNAVAIL", "EPROGMISMATCH", "EPROCUNAVAIL", "ENOLCK", "ENOSYS", "EFTYPE", + /* 80 */ "EAUTH", "ENEEDAUTH", "EIDRM", "ENOMSG", "EOVERFLOW", "ECANCELED", "EILSEQ", "", + /* 88 */ "EDOOFUS", "EBADMSG", "EMULTIHOP", "ENOLINK", "EPROTO", "ENOTCAPABLE", "ECAPMODE", "ENOTRECOVERABLE", + /* 96 */ "EOWNERDEAD", "EINTEGRITY", +}; +#else +#error "errno name table not defined for this platform" +#endif +// clang-format on + +std::string_view +errno_short_name(int e) +{ + if (e >= 0 && e < static_cast(sizeof(ERRNO_NAMES) / sizeof(ERRNO_NAMES[0])) && !ERRNO_NAMES[e].empty()) { + return ERRNO_NAMES[e]; + } + return "Unknown"sv; +} } // namespace BufferWriter & @@ -910,8 +906,9 @@ bwformat(BufferWriter &w, bwf::Spec const &spec, std::error_code const &ec) { if (spec.has_numeric_type()) { // if numeric type, print just the numeric part. bwformat(w, spec, ec.value()); } else { - if ((&ec.category() == G_CAT || &ec.category() == S_CAT) && swoc::ERRNO_RANGE.contains(ec.value())) { - bwformat(w, spec, swoc::ERRNO_SHORT_NAME[ec.value()]); + auto short_name = (&ec.category() == G_CAT || &ec.category() == S_CAT) ? errno_short_name(ec.value()) : "Unknown"sv; + if (short_name != "Unknown"sv) { + bwformat(w, spec, short_name); } else { w.write(ec.message()); } From bab8ad5a09d70661b9596184688e736ee9f7a65b Mon Sep 17 00:00:00 2001 From: Bryan Call Date: Thu, 4 Jun 2026 13:45:43 -0700 Subject: [PATCH 2/2] Address review: fix EPERM/EOPNOTSUPP entries, add errno name test The FreeBSD table was missing EPERM (errno 1), so it formatted as "Unknown". Linux and FreeBSD also picked the ENOTSUP alias over the more common EOPNOTSUPP for that value, which changed existing log output. Prefer the canonical name (EAGAIN/EDEADLK/EOPNOTSUPP) and fill in EPERM. Add a test_bw_format case asserting bwf::Errno prints the platform's own symbolic name for EPERM, ETIMEDOUT, and ECONNREFUSED, guarding against the mislabel this PR fixes. --- lib/swoc/src/bw_format.cc | 6 +++--- lib/swoc/unit_tests/test_bw_format.cc | 13 +++++++++++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/lib/swoc/src/bw_format.cc b/lib/swoc/src/bw_format.cc index 0102d46f03c..906e47d4674 100644 --- a/lib/swoc/src/bw_format.cc +++ b/lib/swoc/src/bw_format.cc @@ -763,7 +763,7 @@ static constexpr std::string_view ERRNO_NAMES[] = { /* 64 */ "ENONET", "ENOPKG", "EREMOTE", "ENOLINK", "EADV", "ESRMNT", "ECOMM", "EPROTO", /* 72 */ "EMULTIHOP", "EDOTDOT", "EBADMSG", "EOVERFLOW", "ENOTUNIQ", "EBADFD", "EREMCHG", "ELIBACC", /* 80 */ "ELIBBAD", "ELIBSCN", "ELIBMAX", "ELIBEXEC", "EILSEQ", "ERESTART", "ESTRPIPE", "EUSERS", - /* 88 */ "ENOTSOCK", "EDESTADDRREQ", "EMSGSIZE", "EPROTOTYPE", "ENOPROTOOPT", "EPROTONOSUPPORT", "ESOCKTNOSUPPORT", "ENOTSUP", + /* 88 */ "ENOTSOCK", "EDESTADDRREQ", "EMSGSIZE", "EPROTOTYPE", "ENOPROTOOPT", "EPROTONOSUPPORT", "ESOCKTNOSUPPORT", "EOPNOTSUPP", /* 96 */ "EPFNOSUPPORT", "EAFNOSUPPORT", "EADDRINUSE", "EADDRNOTAVAIL", "ENETDOWN", "ENETUNREACH", "ENETRESET", "ECONNABORTED", /* 104 */ "ECONNRESET", "ENOBUFS", "EISCONN", "ENOTCONN", "ESHUTDOWN", "ETOOMANYREFS", "ETIMEDOUT", "ECONNREFUSED", /* 112 */ "EHOSTDOWN", "EHOSTUNREACH", "EALREADY", "EINPROGRESS", "ESTALE", "EUCLEAN", "ENOTNAM", "ENAVAIL", @@ -789,12 +789,12 @@ static constexpr std::string_view ERRNO_NAMES[] = { }; #elif defined(__FreeBSD__) static constexpr std::string_view ERRNO_NAMES[] = { - /* 0 */ "SUCCESS", "", "ENOENT", "ESRCH", "EINTR", "EIO", "ENXIO", "E2BIG", + /* 0 */ "SUCCESS", "EPERM", "ENOENT", "ESRCH", "EINTR", "EIO", "ENXIO", "E2BIG", /* 8 */ "ENOEXEC", "EBADF", "ECHILD", "EDEADLK", "ENOMEM", "EACCES", "EFAULT", "ENOTBLK", /* 16 */ "EBUSY", "EEXIST", "EXDEV", "ENODEV", "ENOTDIR", "EISDIR", "EINVAL", "ENFILE", /* 24 */ "EMFILE", "ENOTTY", "ETXTBSY", "EFBIG", "ENOSPC", "ESPIPE", "EROFS", "EMLINK", /* 32 */ "EPIPE", "EDOM", "ERANGE", "EAGAIN", "EINPROGRESS", "EALREADY", "ENOTSOCK", "EDESTADDRREQ", - /* 40 */ "EMSGSIZE", "EPROTOTYPE", "ENOPROTOOPT", "EPROTONOSUPPORT", "ESOCKTNOSUPPORT", "ENOTSUP", "EPFNOSUPPORT", "EAFNOSUPPORT", + /* 40 */ "EMSGSIZE", "EPROTOTYPE", "ENOPROTOOPT", "EPROTONOSUPPORT", "ESOCKTNOSUPPORT", "EOPNOTSUPP", "EPFNOSUPPORT", "EAFNOSUPPORT", /* 48 */ "EADDRINUSE", "EADDRNOTAVAIL", "ENETDOWN", "ENETUNREACH", "ENETRESET", "ECONNABORTED", "ECONNRESET", "ENOBUFS", /* 56 */ "EISCONN", "ENOTCONN", "ESHUTDOWN", "ETOOMANYREFS", "ETIMEDOUT", "ECONNREFUSED", "ELOOP", "ENAMETOOLONG", /* 64 */ "EHOSTDOWN", "EHOSTUNREACH", "ENOTEMPTY", "EPROCLIM", "EUSERS", "EDQUOT", "ESTALE", "EREMOTE", diff --git a/lib/swoc/unit_tests/test_bw_format.cc b/lib/swoc/unit_tests/test_bw_format.cc index 2cfb99ad2fd..c31d2c25b52 100644 --- a/lib/swoc/unit_tests/test_bw_format.cc +++ b/lib/swoc/unit_tests/test_bw_format.cc @@ -8,6 +8,7 @@ #include #include #include +#include #include @@ -545,6 +546,18 @@ TEST_CASE("bwstring std formats", "[libswoc][bwprint]") { w.clear().print("{::l}", swoc::bwf::Errno(13)); REQUIRE(w.view() == "Permission denied [13]"sv); + // The symbolic short name must come from the running platform's , + // not a fixed Linux-numbered table. Regression guard for the FreeBSD/macOS + // mislabel where ETIMEDOUT (errno 60 on those platforms) printed as "ENOSTR" + // (apache/trafficserver#13203). Use the macros so each platform checks its + // own numbering. + w.clear().print("{:s:s}", swoc::bwf::Errno(EPERM)); + REQUIRE(w.view() == "EPERM"sv); + w.clear().print("{:s:s}", swoc::bwf::Errno(ETIMEDOUT)); + REQUIRE(w.view() == "ETIMEDOUT"sv); + w.clear().print("{:s:s}", swoc::bwf::Errno(ECONNREFUSED)); + REQUIRE(w.view() == "ECONNREFUSED"sv); + time_t t = 1528484137; // default is GMT w.clear().print("{} is {}", t, swoc::bwf::Date(t));