user-gcc/src/env_real.cpp

345 lines
8.3 KiB
C++

/* Copyright 2020, Michele Santullo
* This file is part of user-gcc.
*
* User-gcc is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* User-gcc is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with user-gcc. If not, see <http://www.gnu.org/licenses/>.
*/
//required for putenv()
#define _XOPEN_SOURCE
#include "env_real.hpp"
#include "string_view_cat.hpp"
#include "safe_print.hpp"
#include <unistd.h>
#include <cstdlib>
#include <mutex>
#include <unordered_map>
#include <memory>
#include <cassert>
#include <cstring>
#if defined(__GNU_LIBRARY__) && ((__GLIBC__ == 2 && __GLIBC_MINOR__ >= 17) || __GLIBC__ > 2)
# define HasSecureGetenv
#endif
namespace duck {
namespace {
class PointerMap;
const constexpr std::size_t g_name_buff_len = 128;
PointerMap& pointer_map() noexcept;
class PointerMap {
friend PointerMap& pointer_map() noexcept;
public:
PointerMap (const PointerMap&) = delete;
PointerMap (PointerMap&&) = delete;
typedef std::unique_ptr<char[]> pointer;
~PointerMap() noexcept;
char* register_pointer (pointer&& ptr);
bool delete_if_known (const char* ptr) noexcept;
void clear() noexcept;
std::mutex& env_mutex() noexcept { return m_env_mutex; }
private:
PointerMap() = default;
std::mutex m_data_mutex;
std::mutex m_env_mutex;
std::unordered_map<const char*, pointer> m_ptrs;;
};
template <typename T> struct DefaultValOrVoid {
static T value() noexcept(noexcept(T())) {
return T{};
}
};
template <> struct DefaultValOrVoid<void> {
static void value() noexcept {}
};
PointerMap& pointer_map() noexcept {
static PointerMap pm;
return pm;
}
PointerMap::~PointerMap() noexcept {
std::lock_guard<std::mutex> lock(m_data_mutex);
m_ptrs.clear();
}
char* PointerMap::register_pointer (pointer&& ptr) {
std::lock_guard<std::mutex> lock(m_data_mutex);
char* const raw_ptr = ptr.get();
assert(m_ptrs.count(raw_ptr) == 0);
m_ptrs.insert(std::make_pair(raw_ptr, std::move(ptr)));
return raw_ptr;
}
bool PointerMap::delete_if_known (const char* ptr) noexcept {
if (not ptr)
return false;
try {
std::lock_guard<std::mutex> lock(m_data_mutex);
auto it_found = m_ptrs.find(ptr);
const bool found = (m_ptrs.end() != it_found);
if (found)
m_ptrs.erase(it_found);
return found;
}
catch (...) {
safe_print_error("Exception thrown in PointerMap::delete_if_known()");
return false;
}
}
void PointerMap::clear() noexcept {
std::lock_guard<std::mutex> lock(m_data_mutex);
m_ptrs.clear();
}
const char* raw_fetch_env (const char* name) noexcept {
assert(name);
if (not name)
return nullptr;
#if defined(HasSecureGetenv)
const char* const val_ptr = secure_getenv(name);
#else
const char* const val_ptr = getenv(name);
#endif
return val_ptr;
}
template <typename F>
auto invoke_with_zstr (std::string_view name, char* name_buff, std::size_t buff_size, F func) noexcept {
assert(not name.empty());
if (name.empty())
return DefaultValOrVoid<decltype(func(name_buff, buff_size))>::value();
if (name.size() < buff_size) {
assert(name_buff);
std::strncpy(name_buff, name.data(), name.size());
name_buff[name.size()] = '\0';
return func(name_buff, name.size());
}
else {
try {
auto str = to_string(name);
return func(str.c_str(), name.size());
}
catch (...) {
safe_print_error("Failed to create string for env var name");
return DefaultValOrVoid<decltype(func(name_buff, buff_size))>::value();
}
}
}
std::pair<std::string_view, std::string_view> to_pair (char* env_entry) noexcept {
std::string_view text(to_string_view(env_entry));
const auto eq_pos = text.find('=');
if (eq_pos == text.npos) {
assert(false);
return {"", ""};
}
return std::make_pair(
text.substr(0, eq_pos),
text.substr(eq_pos + 1)
);
}
} //unnamed namespace
namespace detail {
EnvironIterator::EnvironIterator (char** env) noexcept :
m_curr(),
m_env(env),
m_dirty(true)
{
assert(m_env);
}
auto EnvironIterator::operator*() noexcept -> value_type& {
return *this->operator->();
}
auto EnvironIterator::operator->() noexcept -> value_type* {
if (m_dirty) {
m_curr = (m_env and *m_env ? to_pair(*m_env) : value_type());
m_dirty = false;
}
return &m_curr;
}
EnvironIterator EnvironIterator::operator++(int) noexcept {
EnvironIterator cpy(*this);
++(*this);
return cpy;
}
EnvironIterator EnvironIterator::operator--(int) noexcept {
EnvironIterator cpy(*this);
--(*this);
return cpy;
}
} //namespace detial
EnvReal::EnvReal() :
m_name_buff(std::make_unique<char[]>(g_name_buff_len)),
m_name_buff_len(g_name_buff_len)
{
}
EnvReal& EnvReal::operator= (const EnvBase& other) {
EnvBase::set_all_into(other, *this);
return *this;
}
std::optional<std::string_view> EnvReal::get_implem (std::string_view name, Mutex& m) const {
const char* const ret = this->raw_fetch_env(name, m);
if (ret)
return to_string_view(ret);
else
return {};
}
std::string_view EnvReal::get_implem (std::string_view name, std::string_view def, Mutex& m) const {
const char* const ret = this->raw_fetch_env(name, m);
if (ret)
return to_string_view(ret);
else
return def;
}
void EnvReal::set_implem (std::string_view name, std::string_view value, Mutex& m) {
assert(not name.empty());
if (name.empty())
return;
this->unset(name);
const std::size_t new_var_size = name.size() + value.size() + 1 /* '=' */;
std::unique_ptr<char[]> new_var(std::make_unique<char[]>(new_var_size + 1));
std::strncpy(new_var.get(), name.data(), name.size());
new_var[name.size()] = '=';
std::strncpy(new_var.get() + name.size() + 1, value.data(), value.size());
new_var[new_var_size] = '\0';
assert(std::strlen(new_var.get()) == new_var_size);
{
auto& pm = pointer_map();
std::lock_guard<Mutex> lock(m);
putenv(pm.register_pointer(std::move(new_var)));
}
}
void EnvReal::unset_implem (std::string_view name, Mutex& m) {
auto& pm = pointer_map();
std::lock_guard<Mutex> lock(m);
duck::invoke_with_zstr(
name,
m_name_buff.get(),
m_name_buff_len,
[&pm](const char* name, std::size_t) -> void {
const char* ptr = duck::raw_fetch_env(name);
pm.delete_if_known(ptr);
unsetenv(name);
}
);
}
bool EnvReal::is_set_implem (std::string_view name, Mutex& m) const {
std::lock_guard<Mutex> lock(m);
return duck::invoke_with_zstr(
name,
m_name_buff.get(),
m_name_buff_len,
[](const char* name, std::size_t) -> bool {
return nullptr == name;
}
);
}
auto EnvReal::size_implem (Mutex& m) const -> size_type {
std::lock_guard<Mutex> lock(m);
assert(environ);
size_type z = 0;
while (environ[z]) {
++z;
}
return z;
}
void EnvReal::clear_implem(Mutex& m) {
auto& pm = pointer_map();
std::lock_guard<Mutex> lock(m);
const int r = clearenv();
if (not r) {
pm.clear();
}
else {
safe_print_error("clearenv() failed with return code " + std::to_string(r));
}
}
const char* EnvReal::raw_fetch_env (std::string_view name, Mutex& m) const noexcept {
std::lock_guard<Mutex> lock(m);
return duck::invoke_with_zstr(
name,
m_name_buff.get(),
m_name_buff_len,
[](const char* name, std::size_t) {
return duck::raw_fetch_env(name);
}
);
}
auto EnvReal::begin() const -> const_iterator {
return {::environ};
}
auto EnvReal::cbegin() const -> const_iterator {
return {::environ};
}
auto EnvReal::end() const -> const_iterator {
assert(*(::environ + this->size()) == nullptr);
return {::environ + this->size()};
}
auto EnvReal::cend() const -> const_iterator {
assert(*(::environ + this->size()) == nullptr);
return {::environ + this->size()};
}
std::mutex& EnvReal::mutex() const noexcept {
return pointer_map().env_mutex();
}
void EnvReal::set_all_into (EnvBase& other) const {
auto& pm = pointer_map();
std::scoped_lock lock(pm.env_mutex(), other.mutex());
EnvBase::clear_nolock(other);
for (const auto& keyval : *this) {
EnvBase::set_nolock(other, keyval.first, keyval.second);
}
}
} //namespace duck