Skip to content

Commit

Permalink
Get rid of string-based option access
Browse files Browse the repository at this point in the history
  • Loading branch information
lvxnull committed Oct 21, 2023
1 parent 4569eaa commit b05d24c
Show file tree
Hide file tree
Showing 5 changed files with 788 additions and 673 deletions.
126 changes: 57 additions & 69 deletions src/btop_config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ tab-size = 4
#include <exception>
#include <fstream>
#include <ranges>
#include <stdexcept>
#include <string_view>

#include <fmt/core.h>
Expand All @@ -44,7 +45,7 @@ namespace Config {
atomic<bool> locked (false);
atomic<bool> writelock (false);
bool write_new;
robin_hood::unordered_flat_set<std::string_view> cached{};
unordered_flat_map<uint32_t, ConfigType> cached{};
ConfigSet current = ConfigSet::with_defaults();
ConfigSet cache{};

Expand Down Expand Up @@ -150,34 +151,21 @@ namespace Config {

string validError;

bool intValid(const std::string_view name, const string& value) {
int i_value;
bool to_int(const string& value, int& result) {
try {
i_value = stoi(value);
}
catch (const std::invalid_argument&) {
result = stoi(value);
} catch (const std::invalid_argument&) {
validError = "Invalid numerical value!";
return false;
}
catch (const std::out_of_range&) {
} catch (const std::out_of_range&) {
validError = "Value out of range!";
return false;
}
catch (const std::exception& e) {
return false;
} catch (const std::exception& e) {
validError = string{e.what()};
return false;
}

if (name == "update_ms" and i_value < 100)
validError = "Config value update_ms set too low (<100).";

else if (name == "update_ms" and i_value > 86400000)
validError = "Config value update_ms set too high (>86400000).";

else
return true;

return false;
return true;
}

bool stringValid(const std::string_view name, const string& value) {
Expand Down Expand Up @@ -242,18 +230,16 @@ namespace Config {
dynamic_set<T>(dest, offset, dynamic_get<T>(src, offset));
}

string getAsString(const std::string_view name) {
auto& entry = parse_table.at(name);
switch(entry.type) {
case ConfigType::BOOL:
return dynamic_get<bool>(current, entry.offset) ? "True" : "False";
case ConfigType::INT:
return to_string(dynamic_get<int>(current, entry.offset));
case ConfigType::STRING:
return dynamic_get<string>(current, entry.offset);
default:
throw std::logic_error("Not implemented");
string getAsString(const size_t offset) {
switch(offset) {
#define X(T, name, v, desc) \
case CONFIG_OFFSET(name): \
return details::to_string<T>(current, offset);
OPTIONS_LIST
#undef X
}

return "";
}

void unlock() {
Expand All @@ -270,17 +256,15 @@ namespace Config {
}

for(auto& item: cached) {
auto& data = parse_table.at(item);

switch(data.type) {
switch(item.second) {
case ConfigType::STRING:
copy_field<string>(current, cache, data.offset);
copy_field<string>(current, cache, item.first);
break;
case ConfigType::INT:
copy_field<int>(current, cache, data.offset);
copy_field<int>(current, cache, item.first);
break;
case ConfigType::BOOL:
copy_field<bool>(current, cache, data.offset);
copy_field<bool>(current, cache, item.first);
break;
}
}
Expand Down Expand Up @@ -350,23 +334,23 @@ namespace Config {
}
cread >> std::ws;

int intValue;
switch(found->second.type) {
case ConfigType::BOOL:
cread >> value;
if (not isbool(value))
load_warnings.push_back("Got an invalid bool value for config name: " + name);
else
try {
dynamic_set(set, found->second.offset, stobool(value));
} catch(std::invalid_argument&) {
load_warnings.push_back("Got an invalid bool value for config name: " + name);
}
break;
case ConfigType::INT:
cread >> value;
if (not isint(value))
load_warnings.push_back("Got an invalid integer value for config name: " + name);
else if (not intValid(name, value)) {
if(to_int(value, intValue) and validate(found->second.offset, intValue)) {
dynamic_set(set, found->second.offset, intValue);
} else {
load_warnings.push_back(validError);
}
else
dynamic_set(set, found->second.offset, stoi(value));
break;
case ConfigType::STRING:
if (cread.peek() == '"') {
Expand All @@ -375,10 +359,11 @@ namespace Config {
}
else cread >> value;

if (not stringValid(name, value))
load_warnings.push_back(validError);
else
if (validate(found->second.offset, value)) {
dynamic_set(set, found->second.offset, value);
} else {
load_warnings.push_back(validError);
}
break;
}

Expand All @@ -391,34 +376,37 @@ namespace Config {
}
}

template<typename T>
inline void write_field(std::ofstream& stream, const std::string_view name, const T& value, const std::string_view description) {
stream << "\n\n";
if(not description.empty()) {
stream << description << '\n';
}

stream << name << " = ";

if constexpr(std::is_same_v<T, bool>) {
stream << (value ? "True" : "False");
}

if constexpr(std::is_same_v<T, int>) {
stream << value;
}

if constexpr(std::is_same_v<T, string>) {
stream << '"' << value << '"';
}
}

void write() {
if (conf_file.empty() or not write_new) return;
Logger::debug("Writing new config file");
if (geteuid() != Global::real_uid and seteuid(Global::real_uid) != 0) return;
std::ofstream cwrite(conf_file, std::ios::trunc);
auto write_field = [&](const std::string_view name, const std::string_view description) {
cwrite << "\n\n";
if(not description.empty()) {
cwrite << description << "\n";
}
cwrite << name << " = ";
auto& data = parse_table.at(name);
switch(data.type) {
case ConfigType::BOOL:
cwrite << (dynamic_get<bool>(current, data.offset) ? "True" : "False");
break;
case ConfigType::INT:
cwrite << dynamic_get<int>(current, data.offset);
break;
case ConfigType::STRING:
cwrite << '"' << dynamic_get<string>(current, data.offset) << '"';
break;
}
};

if (cwrite.good()) {
cwrite << "#? Config file for btop v. " << Global::Version;
#define X(T, name, v, desc) write_field(#name, desc);
#define X(T, name, v, desc) write_field(cwrite, #name, current.name, desc);
OPTIONS_WRITABLE_LIST
#undef X
}
Expand Down
122 changes: 107 additions & 15 deletions src/btop_config.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ tab-size = 4

#pragma once

#include "btop_tools.hpp"
#include <string>
#include <vector>
#include <filesystem>
Expand Down Expand Up @@ -196,9 +197,10 @@ using robin_hood::unordered_flat_map;

#define OPTIONS_LIST OPTIONS_WRITABLE_LIST OPTIONS_INTERNAL_LIST

#define CONFIG_OFFSET(name) offsetof(Config::ConfigSet, name)
#define CONFIG_SET(name, v) Config::set< \
decltype(std::declval<Config::ConfigSet>().name) \
>(offsetof(Config::ConfigSet, name), #name, v, Config::is_writable(#name))
>(CONFIG_OFFSET(name), v, Config::is_writable(#name))

//* Functions and variables for reading and writing the btop config file
namespace Config {
Expand Down Expand Up @@ -226,12 +228,6 @@ namespace Config {
};
}

struct ParseData {
// Offset will be 32bit to save space/make this fit in a register
uint32_t offset;
ConfigType type;
};

struct ConfigSet {
#define X(T, name, v, desc) T name;
OPTIONS_LIST
Expand All @@ -246,10 +242,21 @@ namespace Config {
}
};

struct ParseData {
// Offset will be 32bit to save space/make this fit in a register
uint32_t offset;
ConfigType type;
};

template<typename T>
constexpr inline ConfigType type_enum() {
return details::TypeToEnum<T>::value;
}

const unordered_flat_map<std::string_view, ParseData> parse_table = {
#define X(T, name, v, desc) { #name, { \
.offset = offsetof(ConfigSet, name), \
.type = Config::details::TypeToEnum<decltype(std::declval<ConfigSet>().name)>::value, \
.offset = CONFIG_OFFSET(name), \
.type = Config::type_enum<decltype(std::declval<ConfigSet>().name)>(), \
} },
OPTIONS_LIST
#undef X
Expand All @@ -267,7 +274,7 @@ namespace Config {
extern vector<string> preset_list;
extern vector<string> available_batteries;
extern int current_preset;
extern robin_hood::unordered_flat_set<std::string_view> cached;
extern unordered_flat_map<uint32_t, ConfigType> cached;

constexpr static bool is_writable(const std::string_view option) {
#define X(T, name, v, desc) if(option == #name) return false;
Expand Down Expand Up @@ -297,7 +304,7 @@ namespace Config {

bool _locked();

string getAsString(const std::string_view name);
string getAsString(const size_t offset);

template<typename T>
inline const T& dynamic_get(const ConfigSet& src, size_t offset) {
Expand All @@ -310,18 +317,103 @@ namespace Config {
}

template<typename T>
void set(const size_t offset, const std::string_view name, const T& value, const bool writable) {
void set(const size_t offset, const T& value, const bool writable) {
bool locked = _locked();
auto& set = get_mut(locked, writable);
if(locked)
cached.insert(name);
cached[offset] = type_enum<T>();
dynamic_set(set, offset, value);
}

namespace details {
template<typename T>
inline string to_string(const ConfigSet& set, const size_t offset);

template<>
inline string to_string<bool>(const ConfigSet& set, const size_t offset) {
return dynamic_get<bool>(set, offset) ? "True" : "False";
}

template<>
inline string to_string<int>(const ConfigSet& set, const size_t offset) {
return std::to_string(dynamic_get<int>(set, offset));
}

template<>
inline string to_string<string>(const ConfigSet& set, const size_t offset) {
return dynamic_get<string>(set, offset);
}
}

extern string validError;

bool intValid(const std::string_view name, const string& value);
bool stringValid(const std::string_view name, const string& value);
//* Converts string to int. True on success, false on error.
bool to_int(const string& value, int& result);

template<typename T>
bool validate(const size_t offset, const T& value, const bool setError = true) {
using namespace Tools;
bool isValid = true;

#define VALIDATE(name, cond, err) case CONFIG_OFFSET(name): \
isValid = (cond); \
if(setError and not isValid) { \
validError = (err); \
} \
break

if constexpr(std::is_same_v<T, int>) {
switch(offset) {
VALIDATE(update_ms, value >= 100 && value <= 86400000,
"Config value update_ms out of range (100 <= x <= 86400000)");
}
}

if constexpr(std::is_same_v<T, string>) {
switch(offset) {
VALIDATE(log_level, Tools::v_contains(Logger::log_levels, value),
"Invalid log level: " + value);
VALIDATE(graph_symbol, Tools::v_contains(valid_graph_symbols, value),
"Invalid graph symbol identifier: " + value);
VALIDATE(graph_symbol_proc, value == "default" || v_contains(valid_graph_symbols, value),
"Invalid graph symbol identifier for graph_symbol_proc: " + value);
VALIDATE(graph_symbol_cpu, value == "default" || v_contains(valid_graph_symbols, value),
"Invalid graph symbol identifier for graph_symbol_cpu: " + value);
VALIDATE(graph_symbol_mem, value == "default" || v_contains(valid_graph_symbols, value),
"Invalid graph symbol identifier for graph_symbol_mem: " + value);
VALIDATE(graph_symbol_net, value == "default" || v_contains(valid_graph_symbols, value),
"Invalid graph symbol identifier for graph_symbol_net: " + value);
VALIDATE(shown_boxes, value.empty() || check_boxes(value),
"Invalid box_name(s) in shown_boxes");
VALIDATE(cpu_core_map, [&]{
const auto maps = ssplit(value);
for (const auto& map : maps) {
const auto map_split = ssplit(map, ':');
if (map_split.size() != 2 || not isint(map_split.at(0)) or not isint(map_split.at(1)))
return false;
}
return true;
}(), "Invalid formatting of cpu_core_map!");
VALIDATE(io_graph_speeds, [&]{
const auto maps = ssplit(value);
for (const auto& map : maps) {
const auto map_split = ssplit(map, ':');
if (map_split.size() != 2 || map_split.at(0).empty() or not isint(map_split.at(1)))
return false;
}
return true;
}(), "Invalid formatting of io_graph_speeds!");

case CONFIG_OFFSET(presets):
return presetsValid(value);
}
}

#undef VALIDATE

return isValid;

}

//* Lock config and cache changes until unlocked
void lock();
Expand Down
Loading

0 comments on commit b05d24c

Please sign in to comment.