Skip to content

Conversation

@aliihsancengiz
Copy link
Contributor

@aliihsancengiz aliihsancengiz commented Dec 24, 2025

Closes #4568

Related: #4568

What's new?

Replace strerror with thread safe version of strerror_r

How to test

Checklist

  • Tests added and pass
  • Adequate documentation added
  • (optional) Added Screenshots or videos

@aliihsancengiz aliihsancengiz requested a review from a team as a code owner December 24, 2025 13:16
@aliihsancengiz aliihsancengiz force-pushed the make-errno-to-string-thread-safe branch 3 times, most recently from 7db9e59 to 873b35f Compare December 27, 2025 18:51
Copy link
Contributor

@AlanGriffiths AlanGriffiths left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Related: #4586

How is this related?

Comment on lines 26 to 54
namespace detail {

// POSIX strerror_r returns int, GNU returns char*.
// This overload set normalizes both to std::string.
inline std::string strerror_r_to_string(int rc, const char* buf) {
// POSIX: rc == 0 on success. On failure, buf may contain something,
// but still provide a deterministic message.
if (rc == 0 && buf && *buf) return std::string(buf);
return "Unknown error (" + std::to_string(rc) + ")";
}

inline std::string strerror_r_to_string(const char* msg, const char* /*buf*/) {
// GNU: returns pointer to message (may or may not be buf).
if (msg && *msg) return std::string(msg);
return "Unknown error";
}


// This overload set normalizes both to char*
inline const char* strerror_r_to_cstr(int /*ret*/, const char* buf) noexcept {
return buf; // POSIX
}

inline const char* strerror_r_to_cstr(char* ret, const char* /*buf*/) noexcept {
return ret ? ret : "Unknown error"; // GNU
}


} // namespace detail
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this detail would be better encapsulated in a .cpp file

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like a typo

Comment on lines 57 to 64
// Thread-safe errno to string conversion.
inline std::string errno_to_string(int err) {
thread_local char buf[256] = {};

// Call strerror_r and normalize both GNU/POSIX return types.
auto ret = ::strerror_r(err, buf, sizeof(buf));
return detail::strerror_r_to_string(ret, buf);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this? I can see some uses, but errno_to_cstr() seems adequate in all these cases.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was my initial opinion, but I think this uses stack memory after returning. See this comment

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, after Alan clearing up thread_local to me. I agree with this, we can just use the cstr version and wrap in in std::string{...} in all the places that require it.

Comment on lines 67 to 73
inline const char* errno_to_cstr(int err) noexcept {
thread_local char buf[256] = {};

// Call strerror_r and normalize both GNU/POSIX return types.
auto ret = ::strerror_r(err, buf, sizeof(buf));
return detail::strerror_r_to_cstr(ret, buf);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The implementation would be better encapsulated in a .cpp file

#ifndef MIR_ERRNO_UTILS_H_
#define MIR_ERRNO_UTILS_H_

#include <cerrno>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we include <cerrno>?

*/

#include "server_example_test_client.h"
#include "mir/errno_utils.h"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#include "mir/errno_utils.h"
#include <mir/errno_utils.h>

This occurs with every inclusion of this header. I won't point it out in every file since it would be too annoying.
Checking the style guide, we forgot to update it to point this out. Opened #4597 to fix this.


namespace mir
{
namespace detail {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe we don't follow the detail pattern in the codebase. Can you split the implementation details into their own source file?

test_lockable_callback.cpp
test_module_deleter.cpp
test_posix_rw_mutex.cpp
test_posix_rw_mutex.cpp
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While this is a correct fix. I'm not sure if we should sneak it in with this PR

@aliihsancengiz aliihsancengiz force-pushed the make-errno-to-string-thread-safe branch from 0f2832a to b624643 Compare January 5, 2026 19:29
@aliihsancengiz
Copy link
Contributor Author

aliihsancengiz commented Jan 5, 2026

Hey @AlanGriffiths and @tarek-y-ismail , thanks for the review.
I have rebased and addressed comments. Please find the first iteration.

  • Got rid of errno_to_string
  • Splitted implementation into .cpp file
  • Some include tweaking
  • exported mir::errno_to_cstr*; for symbol map, i did this manually, but not sure that's the right way.

Thanks.

Comment on lines 25 to 28
#include <mir/fd.h>
#include <mir/emergency_cleanup_registry.h>
#include "ioctl_vt_switcher.h"
#include <mir/errno_utils.h>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#include <mir/fd.h>
#include <mir/emergency_cleanup_registry.h>
#include "ioctl_vt_switcher.h"
#include <mir/errno_utils.h>
#include <mir/errno_utils.h>

mir::dispatch::ThreadedDispatcher::remove_thread*;
mir::dispatch::epoll_to_fd_event*;
mir::dispatch::fd_event_to_epoll*;
mir::errno_to_cstr*;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is in the wrong stanza (the symbol didn't exist in Mir-2.22).

You need to add it in a new MIR_COMMON_2.26 stanza. Vis:

MIR_COMMON_2.26 {
global:
  extern "C++" {
    mir::errno_to_cstr*;
  };
} MIR_COMMON_INTERNAL_2.24;

(That may look confusing, but there's a pre-existing issue with abusing "_INTERNAL_". We can't address that without breaking ABI.)


#include <mir/errno_utils.h>

TEST(ErrnoToCstr, ReturnsNonNullAndNonEmpty) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Project style is snake_case for test names and braces opened on the next line.

Suggested change
TEST(ErrnoToCstr, ReturnsNonNullAndNonEmpty) {
TEST(ErrnoToCstr, returns_non_null_and_non_empty)
{

@aliihsancengiz aliihsancengiz force-pushed the make-errno-to-string-thread-safe branch from 1a2cd3b to ae187fc Compare January 6, 2026 16:15
Comment on lines 26 to 28
#include <mir/fd.h>
#include <mir/emergency_cleanup_registry.h>
#include "ioctl_vt_switcher.h"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was not intended; indeed, it reverted.

#include <thread>

#include <mir/errno_utils.h>
TEST(ErrnoToCstr, returns_non_null_and_non_empty) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Project style is to have the opening brace on the next line

@aliihsancengiz aliihsancengiz force-pushed the make-errno-to-string-thread-safe branch from ae187fc to addb3f2 Compare January 6, 2026 16:51
Comment on lines 24 to 27
// This overload set normalizes both to char*
// inline const char* strerror_r_to_cstr(int ret, const char* buf) noexcept;
// inline const char* strerror_r_to_cstr(char* ret, const char* buf) noexcept;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confusing comment

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indeed, a leftover from clean-up, Thanks.

@aliihsancengiz aliihsancengiz force-pushed the make-errno-to-string-thread-safe branch from addb3f2 to deddc16 Compare January 6, 2026 18:27
Copy link
Contributor

@AlanGriffiths AlanGriffiths left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple of style tweaks

#ifndef MIR_ERRNO_UTILS_H_
#define MIR_ERRNO_UTILS_H_

#include <string>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not needed

Copy link
Contributor

@AlanGriffiths AlanGriffiths left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A couple of style tweaks

Pushed these to avoid another review cycle

@AlanGriffiths AlanGriffiths requested a review from Copilot January 7, 2026 10:03
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR replaces the thread-unsafe strerror function with the thread-safe strerror_r across the codebase, addressing issue #4568. The implementation provides a wrapper function mir::errno_to_cstr that handles differences between GNU and POSIX versions of strerror_r.

Key changes:

  • Introduces mir::errno_to_cstr() as a thread-safe replacement for strerror()
  • Adds comprehensive unit tests for thread safety and correctness
  • Updates all occurrences of strerror() throughout the codebase to use the new wrapper

Reviewed changes

Copilot reviewed 30 out of 31 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
include/common/mir/errno_utils.h Declares the thread-safe errno conversion function
src/common/errno_utils.cpp Implements thread-safe errno-to-string conversion using strerror_r with GNU/POSIX compatibility
tests/unit-tests/test_errno_utils.cpp Adds unit tests verifying thread safety and correctness of errno_to_cstr
tests/unit-tests/CMakeLists.txt Registers the new test file in the build system
src/common/CMakeLists.txt Adds errno_utils.cpp to the build
[multiple platform/server files] Replaces strerror calls with mir::errno_to_cstr for thread safety

Copy link
Contributor

@jhenstridge jhenstridge left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The use of thread_local means the thread safety guarantees are similar to those for plain strerror in modern glibc (although now the return value will always be overwritten on a second call to errno_to_cstr on the same thread).

If we're doing this to improve safety, would a std::string return be more appropriate?

// Since we are normalizing between GNU/POSIX impl, one of function will be unused at compile time

[[maybe_unused]]
inline const char* strerror_r_to_cstr(int /*ret*/, const char* buf) noexcept
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If ret is not zero, then buf could contain garbage. It should probably return "Unknown error" in this case, similar to what you've done in the GNU version.

auto ret = ::strerror_r(err, buf, sizeof(buf));
return strerror_r_to_cstr(ret, buf);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it really worth adding support for two versions of strerror_r? It basically means we'll have a code path that is unused for most of our testing.

Given that this code is in it's own source file now, we know what environment it is being compiled in. It would probably be simpler to put #undef _GNU_SOURCE before the includes, and use an assert to ensure we have the POSIX version. Something like:

static_assert(std::is_same_v<decltype(ret), int>);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That would be a better aproach indeed.
But I am not sure undefining _GNU_SOURCE will force the compiler to use a POSIX/XSI-compliant version.

At least, when I apply it, my compiler is unable to find the POSIX version.

gcc (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0
g++ (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0
ldd (Ubuntu GLIBC 2.39-0ubuntu8.6) 2.39

Maybe i am missing something?

@AlanGriffiths
Copy link
Contributor

The use of thread_local means the thread safety guarantees are similar to those for plain strerror in modern glibc

We don't know we're on glibc. For example, Alpine uses musl

@aliihsancengiz aliihsancengiz force-pushed the make-errno-to-string-thread-safe branch from 1027a37 to ec3755f Compare January 8, 2026 20:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Replace strerror with strerror_r

4 participants