-
Notifications
You must be signed in to change notification settings - Fork 119
feature: replace strerror with thread safe version( strerror_r ) #4590
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
feature: replace strerror with thread safe version( strerror_r ) #4590
Conversation
7db9e59 to
873b35f
Compare
AlanGriffiths
left a comment
There was a problem hiding this 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?
include/common/mir/errno_utils.h
Outdated
| 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 |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like a typo
include/common/mir/errno_utils.h
Outdated
| // 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); | ||
| } |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
include/common/mir/errno_utils.h
Outdated
| 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); | ||
| } |
There was a problem hiding this comment.
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
include/common/mir/errno_utils.h
Outdated
| #ifndef MIR_ERRNO_UTILS_H_ | ||
| #define MIR_ERRNO_UTILS_H_ | ||
|
|
||
| #include <cerrno> |
There was a problem hiding this comment.
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" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| #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.
include/common/mir/errno_utils.h
Outdated
|
|
||
| namespace mir | ||
| { | ||
| namespace detail { |
There was a problem hiding this comment.
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?
tests/unit-tests/CMakeLists.txt
Outdated
| test_lockable_callback.cpp | ||
| test_module_deleter.cpp | ||
| test_posix_rw_mutex.cpp | ||
| test_posix_rw_mutex.cpp |
There was a problem hiding this comment.
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
0f2832a to
b624643
Compare
|
Hey @AlanGriffiths and @tarek-y-ismail , thanks for the review.
Thanks. |
| #include <mir/fd.h> | ||
| #include <mir/emergency_cleanup_registry.h> | ||
| #include "ioctl_vt_switcher.h" | ||
| #include <mir/errno_utils.h> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| #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> |
src/common/symbols.map
Outdated
| mir::dispatch::ThreadedDispatcher::remove_thread*; | ||
| mir::dispatch::epoll_to_fd_event*; | ||
| mir::dispatch::fd_event_to_epoll*; | ||
| mir::errno_to_cstr*; |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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.
| TEST(ErrnoToCstr, ReturnsNonNullAndNonEmpty) { | |
| TEST(ErrnoToCstr, returns_non_null_and_non_empty) | |
| { |
1a2cd3b to
ae187fc
Compare
| #include <mir/fd.h> | ||
| #include <mir/emergency_cleanup_registry.h> | ||
| #include "ioctl_vt_switcher.h" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why?
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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
ae187fc to
addb3f2
Compare
include/common/mir/errno_utils.h
Outdated
| // 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; | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Confusing comment
There was a problem hiding this comment.
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.
addb3f2 to
deddc16
Compare
AlanGriffiths
left a comment
There was a problem hiding this 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
include/common/mir/errno_utils.h
Outdated
| #ifndef MIR_ERRNO_UTILS_H_ | ||
| #define MIR_ERRNO_UTILS_H_ | ||
|
|
||
| #include <string> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not needed
AlanGriffiths
left a comment
There was a problem hiding this 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
There was a problem hiding this 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 forstrerror() - 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 |
jhenstridge
left a comment
There was a problem hiding this 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?
src/common/errno_utils.cpp
Outdated
| // 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 |
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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>);
There was a problem hiding this comment.
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?
We don't know we're on glibc. For example, Alpine uses musl |
1027a37 to
ec3755f
Compare
Closes #4568
Related: #4568
What's new?
Replace strerror with thread safe version of strerror_r
How to test
Checklist