Skip to content
Snippets Groups Projects
  • Florian Schmaus's avatar
    18174680
    [LinuxVersion] Use "Construct on first use" idiom and add mutex · 18174680
    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.
    18174680
    History
    [LinuxVersion] Use "Construct on first use" idiom and add mutex
    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.
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