From 4cc1aa491ebde2ad2c59b81d67caf81dbd7e8950 Mon Sep 17 00:00:00 2001 From: King_DuckZ Date: Mon, 13 Apr 2020 14:14:35 +0200 Subject: [PATCH] Add a still incomplete EnvReal class. So far it allows to read and modify environment variables. --- src/env_base.cpp | 24 +++++ src/env_base.hpp | 46 ++++++++ src/env_real.cpp | 266 +++++++++++++++++++++++++++++++++++++++++++++++ src/env_real.hpp | 44 ++++++++ src/meson.build | 2 + 5 files changed, 382 insertions(+) create mode 100644 src/env_base.cpp create mode 100644 src/env_base.hpp create mode 100644 src/env_real.cpp create mode 100644 src/env_real.hpp diff --git a/src/env_base.cpp b/src/env_base.cpp new file mode 100644 index 0000000..1cb8e00 --- /dev/null +++ b/src/env_base.cpp @@ -0,0 +1,24 @@ +/* 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 . + */ + +#include "env_base.hpp" + +namespace duck { + bool EnvBase::empty() const noexcept { + return 0 == this->size(); + } +} //namespace duck diff --git a/src/env_base.hpp b/src/env_base.hpp new file mode 100644 index 0000000..ad68f52 --- /dev/null +++ b/src/env_base.hpp @@ -0,0 +1,46 @@ +/* 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 . + */ + +#pragma once + +#include +#include +#include + +namespace duck { + class EnvBase { + public: + typedef std::string_view mapped_type; + typedef std::string_view key_type; + typedef std::pair value_type; + typedef std::size_t size_type; + typedef std::ptrdiff_t difference_type; + + EnvBase() = default; + virtual ~EnvBase() noexcept = default; + + virtual std::string_view operator[] (std::string_view name) const noexcept = 0; + virtual std::optional get (std::string_view name) const noexcept = 0; + virtual std::string_view get (std::string_view name, std::string_view def) const noexcept = 0; + virtual void set (std::string_view name, std::string_view value) = 0; + virtual void unset (std::string_view name) noexcept = 0; + virtual bool is_set (std::string_view name) const noexcept = 0; + virtual size_type size() const noexcept = 0; + virtual bool empty() const noexcept; + virtual void clear() noexcept = 0; + }; +} //namespace duck diff --git a/src/env_real.cpp b/src/env_real.cpp new file mode 100644 index 0000000..bd3903f --- /dev/null +++ b/src/env_real.cpp @@ -0,0 +1,266 @@ +/* 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 . + */ + +//required for putenv() +#define _XOPEN_SOURCE + +#include "env_real.hpp" +#include "string_view_cat.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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(); + + class PointerMap { + friend PointerMap& pointer_map(); + public: + PointerMap (const PointerMap&) = delete; + PointerMap (PointerMap&&) = delete; + + typedef std::unique_ptr 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 m_ptrs;; + }; + + template struct DefaultValOrVoid { + static T value() noexcept(noexcept(T())) { + return T{}; + } + }; + template <> struct DefaultValOrVoid { + static void value() noexcept {} + }; + + void safe_print_error (std::string_view msg) noexcept { + try { + std::cerr << msg << std::endl; + } + catch (...) { + } + } + + PointerMap& pointer_map() { + static PointerMap pm; + return pm; + } + + PointerMap::~PointerMap() noexcept { + std::lock_guard lock(m_data_mutex); + m_ptrs.clear(); + } + + char* PointerMap::register_pointer (pointer&& ptr) { + std::lock_guard 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 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 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 + 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::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::value(); + } + } + } +} //unnamed namespace + +EnvReal::EnvReal() : + m_name_buff(std::make_unique(g_name_buff_len)), + m_name_buff_len(g_name_buff_len) +{ +} + +std::string_view EnvReal::operator[] (std::string_view name) const noexcept { + return this->get(name, ""); +} + +std::optional EnvReal::get (std::string_view name) const noexcept { + const char* const ret = this->raw_fetch_env(name); + if (ret) + return to_string_view(ret); + else + return {}; +} + +std::string_view EnvReal::get (std::string_view name, std::string_view def) const noexcept { + const char* const ret = this->raw_fetch_env(name); + if (ret) + return to_string_view(ret); + else + return def; +} + +void EnvReal::set (std::string_view name, std::string_view value) { + 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 new_var(std::make_unique(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 lock(pm.env_mutex()); + putenv(pm.register_pointer(std::move(new_var))); + } +} + +void EnvReal::unset (std::string_view name) noexcept { + auto& pm = pointer_map(); + std::lock_guard lock(pm.env_mutex()); + + 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 (std::string_view name) const noexcept { +} + +auto EnvReal::size() const noexcept -> size_type { + auto& pm = pointer_map(); + std::lock_guard lock(pm.env_mutex()); + + assert(environ); + size_type z = 0; + while (environ[z]) { + ++z; + } + return z; +} + +void EnvReal::clear() noexcept { + auto& pm = pointer_map(); + std::lock_guard lock(pm.env_mutex()); + 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) const noexcept { + auto& pm = pointer_map(); + std::lock_guard lock(pm.env_mutex()); + + 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); + } + ); +} + +} //namespace duck diff --git a/src/env_real.hpp b/src/env_real.hpp new file mode 100644 index 0000000..ab90e79 --- /dev/null +++ b/src/env_real.hpp @@ -0,0 +1,44 @@ +/* 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 . + */ + +#pragma once + +#include "env_base.hpp" +#include + +namespace duck { + class EnvReal : public EnvBase { + public: + EnvReal(); + virtual ~EnvReal() noexcept = default; + + virtual std::string_view operator[] (std::string_view name) const noexcept override; + virtual std::optional get (std::string_view name) const noexcept override; + virtual std::string_view get (std::string_view name, std::string_view def) const noexcept override; + virtual void set (std::string_view name, std::string_view value) override; + virtual void unset (std::string_view name) noexcept override; + virtual bool is_set (std::string_view name) const noexcept override; + virtual size_type size() const noexcept override; + virtual void clear() noexcept override; + + private: + const char* raw_fetch_env (std::string_view name) const noexcept; + + std::unique_ptr m_name_buff; + std::size_t m_name_buff_len; + }; +} //namespace duck diff --git a/src/meson.build b/src/meson.build index 9a01898..20eb946 100644 --- a/src/meson.build +++ b/src/meson.build @@ -19,6 +19,8 @@ executable(meson.project_name(), 'to_var_map.cpp', 'load_file.cpp', 'var_map.cpp', + 'env_base.cpp', + 'env_real.cpp', install: true, dependencies: [fslib_dep], include_directories: [cxxopts_incl],