-
Florian Schmaus authored
Use the "Construct on first use" idom for UTS Release (aka Linux version) initialization. The previous implementation was fragle and could lead to segfaults like __s2=0x7fffffffe480 "5.16.10-arch1-1", __s1=<optimized out>) at /usr/include/c++/11.2.0/bits/char_traits.h:409 __n=<optimized out>, __s=0x7fffffffe480 "5.16.10-arch1-1", __d=<optimized out>) at /usr/include/c++/11.2.0/bits/basic_string.h:359 __n=<optimized out>, __s=0x7fffffffe480 "5.16.10-arch1-1", __d=<optimized out>) at /usr/include/c++/11.2.0/bits/basic_string.h:354 __str=..., this=0x7ffff7fbd2a0 <emper::lib::LinuxVersion::globalVersion[abi:cxx11]>) at /usr/include/c++/11.2.0/bits/basic_string.h:739 at ../emper/lib/LinuxVersion.cpp:46 at /usr/include/c++/11.2.0/ext/new_allocator.h:79 if globalVersion's constructor was not yet called. This is the so called "static initialization order fiasco" in C++. While the mutex may not be strictly requires, as we do not call LinuxVersion's non-argument constructor nor getUtsRelease() concurrently, it can not hurt to have one.
Florian Schmaus authoredUse the "Construct on first use" idom for UTS Release (aka Linux version) initialization. The previous implementation was fragle and could lead to segfaults like __s2=0x7fffffffe480 "5.16.10-arch1-1", __s1=<optimized out>) at /usr/include/c++/11.2.0/bits/char_traits.h:409 __n=<optimized out>, __s=0x7fffffffe480 "5.16.10-arch1-1", __d=<optimized out>) at /usr/include/c++/11.2.0/bits/basic_string.h:359 __n=<optimized out>, __s=0x7fffffffe480 "5.16.10-arch1-1", __d=<optimized out>) at /usr/include/c++/11.2.0/bits/basic_string.h:354 __str=..., this=0x7ffff7fbd2a0 <emper::lib::LinuxVersion::globalVersion[abi:cxx11]>) at /usr/include/c++/11.2.0/bits/basic_string.h:739 at ../emper/lib/LinuxVersion.cpp:46 at /usr/include/c++/11.2.0/ext/new_allocator.h:79 if globalVersion's constructor was not yet called. This is the so called "static initialization order fiasco" in C++. While the mutex may not be strictly requires, as we do not call LinuxVersion's non-argument constructor nor getUtsRelease() concurrently, it can not hurt to have one.
LinuxVersion.cpp 2.59 KiB
// SPDX-License-Identifier: LGPL-3.0-or-later
// Copyright © 2021-2022 Florian Fischer, Florian Schmaus
#include "lib/LinuxVersion.hpp"
#include <sys/utsname.h>
#include <cerrno>
#include <cstdlib>
#include <mutex>
#include "Common.hpp"
static auto checked_strtol(const std::string& s) -> long {
static const int DECIMAL = 10;
char* endptr;
const char* startptr = s.c_str();
errno = 0;
long res = strtol(startptr, &endptr, DECIMAL);
if (errno != 0) {
DIE_MSG_ERRNO("strtol failed");
}
if (endptr == startptr) {
DIE_MSG("strtol found no digits in " << s);
}
// TODO: We could report remaining bytes of the string by returning
// std::string(endptr) together with 'res' (potentially using
// std::pair). Not sure if we even need to check if "endptr !=
// startptr + s.size()", or if we could always cosntruct
// std::string(endptr), potentially constructing the empty string.
return res;
}
namespace emper::lib {
auto LinuxVersion::getUtsRelease() -> const std::string& {
// Use "construct (members) on first use idiom" to prevent the "static initialization order
// fiasco/problem".
static std::string utsRelease;
// This mutex will become necessary once we call getUtsRelease(),
// and by implication LinuxVersion's constructor, concurrently. So
// it can not hurt to have it right now.
static std::mutex utsReleaseMutex;
{
const std::lock_guard<std::mutex> lock(utsReleaseMutex);
if (utsRelease.empty()) {
struct utsname buf;
if (uname(&buf)) {
DIE_MSG_ERRNO("Failed to invoke uname()");
}
utsRelease = std::string(buf.release);
}
}
return utsRelease;
}
LinuxVersion::LinuxVersion() { version = getUtsRelease(); }
// Returns 1 if s is smaller, -1 if this is smaller, 0 if equal
auto LinuxVersion::compare(const std::string& s) -> int {
size_t dot_pos, dot_pos2;
size_t last_dot_pos = 0, last_dot_pos2 = 0;
bool was_last = false;
for (;;) {
dot_pos = this->version.find('.', last_dot_pos);
if (dot_pos == std::string::npos) {
was_last = true;
dot_pos = this->version.size();
dot_pos2 = s.size();
} else {
dot_pos2 = s.find('.', last_dot_pos2);
// The string to compare has less dot-separated components
if (dot_pos2 == std::string::npos) return 0;
}
long n1 = checked_strtol(this->version.substr(last_dot_pos, dot_pos - last_dot_pos));
long n2 = checked_strtol(s.substr(last_dot_pos2, dot_pos2 - last_dot_pos2));
if (n1 > n2) return 1;
if (n1 < n2) return -1;
// We ran out of parts to compare the versions must be equal
if (was_last) return 0;
last_dot_pos = dot_pos + 1;
last_dot_pos2 = dot_pos2 + 1;
}
}
} // namespace emper::lib