aboutsummaryrefslogtreecommitdiffstats
path: root/common/core/Configuration.cxx
diff options
context:
space:
mode:
Diffstat (limited to 'common/core/Configuration.cxx')
-rw-r--r--common/core/Configuration.cxx857
1 files changed, 857 insertions, 0 deletions
diff --git a/common/core/Configuration.cxx b/common/core/Configuration.cxx
new file mode 100644
index 00000000..e6affb06
--- /dev/null
+++ b/common/core/Configuration.cxx
@@ -0,0 +1,857 @@
+/* Copyright (C) 2002-2005 RealVNC Ltd. All Rights Reserved.
+ * Copyright 2004-2005 Cendio AB.
+ * Copyright 2017 Peter Astrand <astrand@cendio.se> for Cendio AB
+ * Copyright 2011-2025 Pierre Ossman for Cendio AB
+ *
+ * This 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This software 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 this software; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307,
+ * USA.
+ */
+
+// -=- Configuration.cxx
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <assert.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <string.h>
+
+#include <algorithm>
+#include <stdexcept>
+
+#include <core/Configuration.h>
+#include <core/LogWriter.h>
+#include <core/string.h>
+
+#include <rdr/HexOutStream.h>
+#include <rdr/HexInStream.h>
+
+using namespace core;
+
+static LogWriter vlog("Config");
+
+
+// -=- The Global Configuration object
+Configuration* Configuration::global_ = nullptr;
+
+Configuration* Configuration::global() {
+ if (!global_)
+ global_ = new Configuration();
+ return global_;
+}
+
+// -=- Configuration implementation
+
+bool Configuration::set(const char* paramName, const char* val,
+ bool immutable)
+{
+ for (VoidParameter* current: params) {
+ if (strcasecmp(current->getName(), paramName) == 0) {
+ bool b = current->setParam(val);
+ if (b && immutable)
+ current->setImmutable();
+ return b;
+ }
+ }
+ return false;
+}
+
+VoidParameter* Configuration::get(const char* param)
+{
+ for (VoidParameter* current: params) {
+ if (strcasecmp(current->getName(), param) == 0)
+ return current;
+ }
+ return nullptr;
+}
+
+void Configuration::list(int width, int nameWidth) {
+ for (VoidParameter* current: params) {
+ std::string def_str = current->getDefaultStr();
+ std::string desc_str = current->getDescription();
+ if (!def_str.empty())
+ desc_str += " (default=" + def_str + ")";
+ const char* desc = desc_str.c_str();
+ fprintf(stderr," %-*s -", nameWidth, current->getName());
+ int column = strlen(current->getName());
+ if (column < nameWidth) column = nameWidth;
+ column += 4;
+ while (true) {
+ if (desc[0] == '\0')
+ break;
+
+ int wordLen = strcspn(desc, " \f\n\r\t\v,");
+ if (wordLen == 0) {
+ desc++;
+ continue;
+ }
+
+ if (desc[wordLen] == ',')
+ wordLen++;
+
+ if (column + wordLen + 1 > width) {
+ fprintf(stderr,"\n%*s",nameWidth+4,"");
+ column = nameWidth+4;
+ }
+ fprintf(stderr, " ");
+ column++;
+
+ fprintf(stderr, "%.*s", wordLen, desc);
+ column += wordLen;
+ desc += wordLen;
+ }
+ fprintf(stderr,"\n");
+ }
+}
+
+
+bool Configuration::remove(const char* param) {
+ std::list<VoidParameter*>::iterator iter;
+
+ iter = std::find_if(params.begin(), params.end(),
+ [param](VoidParameter* p) {
+ return strcasecmp(p->getName(), param) == 0;
+ });
+ if (iter == params.end())
+ return false;
+
+ params.erase(iter);
+ return true;
+}
+
+int Configuration::handleArg(int argc, char* argv[], int index)
+{
+ std::string param, val;
+ const char* equal = strchr(argv[index], '=');
+
+ if (equal == argv[index])
+ return 0;
+
+ if (equal) {
+ param.assign(argv[index], equal-argv[index]);
+ val.assign(equal+1);
+ } else {
+ param.assign(argv[index]);
+ }
+
+ if ((param.length() > 0) && (param[0] == '-')) {
+ // allow gnu-style --<option>
+ if ((param.length() > 1) && (param[1] == '-'))
+ param = param.substr(2);
+ else
+ param = param.substr(1);
+ } else {
+ // All command line arguments need either an initial '-', or an '='
+ if (!equal)
+ return 0;
+ }
+
+ if (equal)
+ return set(param.c_str(), val.c_str()) ? 1 : 0;
+
+ for (VoidParameter* current: params) {
+ if (strcasecmp(current->getName(), param.c_str()) != 0)
+ continue;
+
+ // We need to resolve an ambiguity for booleans
+ if (dynamic_cast<BoolParameter*>(current) != nullptr) {
+ if (index+1 < argc) {
+ // FIXME: Should not duplicate the list of values here
+ if ((strcasecmp(argv[index+1], "0") == 0) ||
+ (strcasecmp(argv[index+1], "1") == 0) ||
+ (strcasecmp(argv[index+1], "on") == 0) ||
+ (strcasecmp(argv[index+1], "off") == 0) ||
+ (strcasecmp(argv[index+1], "true") == 0) ||
+ (strcasecmp(argv[index+1], "false") == 0) ||
+ (strcasecmp(argv[index+1], "yes") == 0) ||
+ (strcasecmp(argv[index+1], "no") == 0)) {
+ return current->setParam(argv[index+1]) ? 2 : 0;
+ }
+ }
+ }
+
+ if (current->setParam())
+ return 1;
+
+ if (index+1 >= argc)
+ return 0;
+
+ return current->setParam(argv[index+1]) ? 2 : 0;
+ }
+
+ return 0;
+}
+
+
+// -=- VoidParameter
+
+VoidParameter::VoidParameter(const char* name_, const char* desc_)
+ : immutable(false), name(name_), description(desc_)
+{
+ Configuration *conf;
+
+ conf = Configuration::global();
+ conf->params.push_back(this);
+ conf->params.sort([](const VoidParameter* a, const VoidParameter* b) {
+ return strcasecmp(a->getName(), b->getName()) < 0;
+ });
+}
+
+VoidParameter::~VoidParameter() {
+ Configuration *conf;
+
+ conf = Configuration::global();
+ conf->params.remove(this);
+}
+
+const char*
+VoidParameter::getName() const {
+ return name;
+}
+
+const char*
+VoidParameter::getDescription() const {
+ return description;
+}
+
+bool VoidParameter::setParam() {
+ return false;
+}
+
+bool VoidParameter::isDefault() const {
+ return getDefaultStr() == getValueStr();
+}
+
+void
+VoidParameter::setImmutable() {
+ vlog.debug("Set immutable %s", getName());
+ immutable = true;
+}
+
+// -=- AliasParameter
+
+AliasParameter::AliasParameter(const char* name_, const char* desc_,
+ VoidParameter* param_)
+ : VoidParameter(name_, desc_), param(param_) {
+}
+
+bool
+AliasParameter::setParam(const char* v) {
+ return param->setParam(v);
+}
+
+bool AliasParameter::setParam() {
+ return param->setParam();
+}
+
+std::string AliasParameter::getDefaultStr() const {
+ return "";
+}
+
+std::string AliasParameter::getValueStr() const {
+ return param->getValueStr();
+}
+
+void
+AliasParameter::setImmutable() {
+ vlog.debug("Set immutable %s (Alias)", getName());
+ param->setImmutable();
+}
+
+
+// -=- BoolParameter
+
+BoolParameter::BoolParameter(const char* name_, const char* desc_, bool v)
+: VoidParameter(name_, desc_), value(v), def_value(v) {
+}
+
+bool
+BoolParameter::setParam(const char* v) {
+ if (immutable) return true;
+
+ if (*v == 0 || strcasecmp(v, "1") == 0 || strcasecmp(v, "on") == 0
+ || strcasecmp(v, "true") == 0 || strcasecmp(v, "yes") == 0)
+ setParam(true);
+ else if (strcasecmp(v, "0") == 0 || strcasecmp(v, "off") == 0
+ || strcasecmp(v, "false") == 0 || strcasecmp(v, "no") == 0)
+ setParam(false);
+ else {
+ vlog.error("Bool parameter %s: Invalid value '%s'", getName(), v);
+ return false;
+ }
+
+ return true;
+}
+
+bool BoolParameter::setParam() {
+ setParam(true);
+ return true;
+}
+
+void BoolParameter::setParam(bool b) {
+ if (immutable) return;
+ value = b;
+ vlog.debug("Set %s(Bool) to %s", getName(), getValueStr().c_str());
+}
+
+std::string BoolParameter::getDefaultStr() const {
+ return def_value ? "on" : "off";
+}
+
+std::string BoolParameter::getValueStr() const {
+ return value ? "on" : "off";
+}
+
+BoolParameter::operator bool() const {
+ return value;
+}
+
+// -=- IntParameter
+
+IntParameter::IntParameter(const char* name_, const char* desc_, int v,
+ int minValue_, int maxValue_)
+ : VoidParameter(name_, desc_), value(v), def_value(v),
+ minValue(minValue_), maxValue(maxValue_)
+{
+ if (v < minValue || v > maxValue) {
+ vlog.error("Invalid default value %d for %s", v, getName());
+ throw std::invalid_argument("Invalid default value");
+ }
+}
+
+bool
+IntParameter::setParam(const char* v) {
+ char* end;
+ long n;
+ if (immutable) return true;
+ n = strtol(v, &end, 0);
+ if ((*end != 0) || (n < INT_MIN) || (n > INT_MAX)) {
+ vlog.error("Int parameter %s: Invalid value '%s'", getName(), v);
+ return false;
+ }
+ return setParam(n);
+}
+
+bool
+IntParameter::setParam(int v) {
+ if (immutable) return true;
+ if (v < minValue || v > maxValue) {
+ vlog.error("Int parameter %s: Invalid value '%d'", getName(), v);
+ return false;
+ }
+ vlog.debug("Set %s(Int) to %d", getName(), v);
+ value = v;
+ return true;
+}
+
+std::string IntParameter::getDefaultStr() const {
+ char result[16];
+ sprintf(result, "%d", def_value);
+ return result;
+}
+
+std::string IntParameter::getValueStr() const {
+ char result[16];
+ sprintf(result, "%d", value);
+ return result;
+}
+
+IntParameter::operator int() const {
+ return value;
+}
+
+// -=- StringParameter
+
+StringParameter::StringParameter(const char* name_, const char* desc_,
+ const char* v)
+ : VoidParameter(name_, desc_), value(v?v:""), def_value(v?v:"")
+{
+ if (!v) {
+ vlog.error("Default value <null> for %s not allowed",name_);
+ throw std::invalid_argument("Default value <null> not allowed");
+ }
+}
+
+bool StringParameter::setParam(const char* v) {
+ if (immutable) return true;
+ if (!v)
+ throw std::invalid_argument("setParam(<null>) not allowed");
+ vlog.debug("Set %s(String) to %s", getName(), v);
+ value = v;
+ return true;
+}
+
+std::string StringParameter::getDefaultStr() const {
+ return def_value;
+}
+
+std::string StringParameter::getValueStr() const {
+ return value;
+}
+
+StringParameter::operator const char *() const {
+ return value.c_str();
+}
+
+// -=- EnumParameter
+
+EnumParameter::EnumParameter(const char* name_, const char* desc_,
+ const std::set<const char*>& enums_,
+ const char* v)
+ : VoidParameter(name_, desc_), value(v?v:""), def_value(v?v:"")
+{
+ if (!v) {
+ vlog.error("Default value <null> for %s not allowed", name_);
+ throw std::invalid_argument("Default value <null> not allowed");
+ }
+
+ for (const char* e: enums_) {
+ if (!e) {
+ vlog.error("Enumeration <null> for %s not allowed", name_);
+ throw std::invalid_argument("Enumeration <null> not allowed");
+ }
+ enums.insert(e);
+ }
+
+ if (std::find(enums.begin(), enums.end(), def_value) == enums.end()) {
+ vlog.error("Default value %s for %s is not in list of valid values",
+ def_value.c_str(), name_);
+ throw std::invalid_argument("Default value is not in list of valid values");
+ }
+}
+
+bool EnumParameter::setParam(const char* v)
+{
+ std::set<std::string>::const_iterator iter;
+ if (immutable) return true;
+ if (!v)
+ throw std::invalid_argument("setParam(<null>) not allowed");
+ iter = std::find_if(enums.begin(), enums.end(),
+ [v](const std::string& e) {
+ return strcasecmp(e.c_str(), v) == 0;
+ });
+ if (iter == enums.end()) {
+ vlog.error("Enum parameter %s: Invalid value '%s'", getName(), v);
+ return false;
+ }
+ vlog.debug("Set %s(Enum) to %s", getName(), iter->c_str());
+ value = *iter;
+ return true;
+}
+
+std::string EnumParameter::getDefaultStr() const
+{
+ return def_value;
+}
+
+std::string EnumParameter::getValueStr() const
+{
+ return value;
+}
+
+bool EnumParameter::operator==(const char* other) const
+{
+ return strcasecmp(value.c_str(), other) == 0;
+}
+
+bool EnumParameter::operator==(const std::string& other) const
+{
+ return *this == other.c_str();
+}
+
+bool EnumParameter::operator!=(const char* other) const
+{
+ return strcasecmp(value.c_str(), other) != 0;
+}
+
+bool EnumParameter::operator!=(const std::string& other) const
+{
+ return *this != other.c_str();
+}
+
+// -=- BinaryParameter
+
+BinaryParameter::BinaryParameter(const char* name_, const char* desc_,
+ const uint8_t* v, size_t l)
+: VoidParameter(name_, desc_),
+ value(nullptr), length(0), def_value(nullptr), def_length(0) {
+ if (l) {
+ assert(v);
+ value = new uint8_t[l];
+ length = l;
+ memcpy(value, v, l);
+ def_value = new uint8_t[l];
+ def_length = l;
+ memcpy(def_value, v, l);
+ }
+}
+BinaryParameter::~BinaryParameter() {
+ delete [] value;
+ delete [] def_value;
+}
+
+bool BinaryParameter::setParam(const char* v) {
+ if (immutable) return true;
+ std::vector<uint8_t> newValue = hexToBin(v, strlen(v));
+ if (newValue.empty() && strlen(v) > 0)
+ return false;
+ setParam(newValue.data(), newValue.size());
+ return true;
+}
+
+void BinaryParameter::setParam(const uint8_t* v, size_t len) {
+ if (immutable) return;
+ vlog.debug("Set %s(Binary)", getName());
+ delete [] value;
+ value = nullptr;
+ length = 0;
+ if (len) {
+ assert(v);
+ value = new uint8_t[len];
+ length = len;
+ memcpy(value, v, len);
+ }
+}
+
+std::string BinaryParameter::getDefaultStr() const {
+ return binToHex(def_value, def_length);
+}
+
+std::string BinaryParameter::getValueStr() const {
+ return binToHex(value, length);
+}
+
+std::vector<uint8_t> BinaryParameter::getData() const {
+ std::vector<uint8_t> out(length);
+ memcpy(out.data(), value, length);
+ return out;
+}
+
+// -=- ListParameter template
+
+template<typename ValueType>
+ListParameter<ValueType>::ListParameter(const char* name_,
+ const char* desc_,
+ const ListType& v)
+ : VoidParameter(name_, desc_), value(v), def_value(v)
+{
+}
+
+template<typename ValueType>
+bool ListParameter<ValueType>::setParam(const char* v)
+{
+ std::vector<std::string> entries;
+ ListType new_value;
+
+ if (immutable)
+ return true;
+
+ // setParam({}) ends up as setParam(nullptr)
+ if (v != nullptr)
+ entries = split(v, ',');
+
+ for (std::string& entry : entries) {
+ ValueType e;
+
+ entry.erase(0, entry.find_first_not_of(" \f\n\r\t\v"));
+ entry.erase(entry.find_last_not_of(" \f\n\r\t\v")+1);
+
+ // Special case, entire v was just whitespace
+ if (entry.empty() && (entries.size() == 1))
+ break;
+
+ if (!decodeEntry(entry.c_str(), &e)) {
+ vlog.error("List parameter %s: Invalid value '%s'",
+ getName(), entry.c_str());
+ return false;
+ }
+
+ new_value.push_back(e);
+ }
+
+ return setParam(new_value);
+}
+
+template<typename ValueType>
+bool ListParameter<ValueType>::setParam(const ListType& v)
+{
+ ListType vnorm;
+ if (immutable)
+ return true;
+ for (const ValueType& entry : v) {
+ if (!validateEntry(entry)) {
+ vlog.error("List parameter %s: Invalid value '%s'", getName(),
+ encodeEntry(entry).c_str());
+ return false;
+ }
+ vnorm.push_back(normaliseEntry(entry));
+ }
+ value = vnorm;
+ vlog.debug("set %s(List) to %s", getName(), getValueStr().c_str());
+ return true;
+}
+
+template<typename ValueType>
+std::string ListParameter<ValueType>::getDefaultStr() const
+{
+ std::string result;
+
+ for (ValueType entry : def_value) {
+ // FIXME: Might want to add a space here as well for readability,
+ // but this would sacrifice backward compatibility
+ if (!result.empty())
+ result += ',';
+ result += encodeEntry(entry);
+ }
+
+ return result;
+}
+
+template<typename ValueType>
+std::string ListParameter<ValueType>::getValueStr() const
+{
+ std::string result;
+
+ for (ValueType entry : value) {
+ // FIXME: Might want to add a space here as well for readability,
+ // but this would sacrifice backward compatibility
+ if (!result.empty())
+ result += ',';
+ result += encodeEntry(entry);
+ }
+
+ return result;
+}
+
+template<typename ValueType>
+typename ListParameter<ValueType>::const_iterator ListParameter<ValueType>::begin() const
+{
+ return value.begin();
+}
+
+template<typename ValueType>
+typename ListParameter<ValueType>::const_iterator ListParameter<ValueType>::end() const
+{
+ return value.end();
+}
+
+template<typename ValueType>
+bool ListParameter<ValueType>::validateEntry(const ValueType& /*entry*/) const
+{
+ return true;
+}
+
+template<typename ValueType>
+ValueType ListParameter<ValueType>::normaliseEntry(const ValueType& entry) const
+{
+ return entry;
+}
+
+// -=- IntListParameter
+
+template class core::ListParameter<int>;
+
+IntListParameter::IntListParameter(const char* name_, const char* desc_,
+ const ListType& v,
+ int minValue_, int maxValue_)
+ : ListParameter<int>(name_, desc_, v),
+ minValue(minValue_), maxValue(maxValue_)
+{
+ for (int entry : v) {
+ if (!validateEntry(entry)) {
+ vlog.error("Invalid default value %d for %s", entry, getName());
+ throw std::invalid_argument("Invalid default value");
+ }
+ }
+}
+
+bool IntListParameter::decodeEntry(const char* entry, int* out) const
+{
+ long n;
+ char *end;
+
+ assert(entry);
+ assert(out);
+
+ if (entry[0] == '\0')
+ return false;
+
+ n = strtol(entry, &end, 0);
+ if ((*end != 0) || (n < INT_MIN) || (n > INT_MAX))
+ return false;
+
+ *out = n;
+
+ return true;
+}
+
+std::string IntListParameter::encodeEntry(const int& entry) const
+{
+ char valstr[16];
+ sprintf(valstr, "%d", entry);
+ return valstr;
+}
+
+bool IntListParameter::validateEntry(const int& entry) const
+{
+ return (entry >= minValue) && (entry <= maxValue);
+}
+
+// -=- StringListParameter
+
+template class core::ListParameter<std::string>;
+
+StringListParameter::StringListParameter(const char* name_,
+ const char* desc_,
+ const std::list<const char*>& v_)
+ : ListParameter<std::string>(name_, desc_, {})
+{
+ for (const char* v: v_) {
+ if (!v) {
+ vlog.error("Default value <null> for %s not allowed", name_);
+ throw std::invalid_argument("Default value <null> not allowed");
+ }
+ value.push_back(v);
+ def_value.push_back(v);
+ }
+}
+
+StringListParameter::const_iterator StringListParameter::begin() const
+{
+ return ListParameter<std::string>::begin();
+}
+
+StringListParameter::const_iterator StringListParameter::end() const
+{
+ return ListParameter<std::string>::end();
+}
+
+bool StringListParameter::decodeEntry(const char* entry, std::string* out) const
+{
+ *out = entry;
+ return true;
+}
+
+std::string StringListParameter::encodeEntry(const std::string& entry) const
+{
+ return entry;
+}
+
+// -=- EnumListEntry
+
+EnumListEntry::EnumListEntry(const std::string& v)
+ : value(v)
+{
+}
+
+std::string EnumListEntry::getValueStr() const
+{
+ return value;
+}
+
+bool EnumListEntry::operator==(const char* other) const
+{
+ return strcasecmp(value.c_str(), other) == 0;
+}
+
+bool EnumListEntry::operator==(const std::string& other) const
+{
+ return *this == other.c_str();
+}
+
+bool EnumListEntry::operator!=(const char* other) const
+{
+ return strcasecmp(value.c_str(), other) != 0;
+}
+
+bool EnumListEntry::operator!=(const std::string& other) const
+{
+ return *this != other.c_str();
+}
+
+// -=- EnumListParameter
+
+EnumListParameter::EnumListParameter(const char* name_,
+ const char* desc_,
+ const std::set<const char*>& enums_,
+ const std::list<const char*>& v_)
+ : ListParameter<std::string>(name_, desc_, {})
+{
+ for (const char* v: v_) {
+ if (!v) {
+ vlog.error("Default value <null> for %s not allowed", name_);
+ throw std::invalid_argument("Default value <null> not allowed");
+ }
+ value.push_back(v);
+ def_value.push_back(v);
+ }
+
+ for (const char* e: enums_) {
+ if (!e) {
+ vlog.error("Enumeration <null> for %s not allowed", name_);
+ throw std::invalid_argument("Enumeration <null> not allowed");
+ }
+ enums.insert(e);
+ }
+
+ for (const std::string& def_entry : def_value) {
+ if (std::find(enums.begin(), enums.end(), def_entry) == enums.end()) {
+ vlog.error("Default value %s for %s is not in list of valid values",
+ def_entry.c_str(), name_);
+ throw std::invalid_argument("Default value is not in list of valid values");
+ }
+ }
+}
+
+EnumListParameter::const_iterator EnumListParameter::begin() const
+{
+ return ListParameter<std::string>::begin();
+}
+
+EnumListParameter::const_iterator EnumListParameter::end() const
+{
+ return ListParameter<std::string>::end();
+}
+
+bool EnumListParameter::decodeEntry(const char* entry, std::string* out) const
+{
+ *out = entry;
+ return true;
+}
+
+std::string EnumListParameter::encodeEntry(const std::string& entry) const
+{
+ return entry;
+}
+
+bool EnumListParameter::validateEntry(const std::string& entry) const
+{
+ for (const std::string& e : enums) {
+ if (strcasecmp(e.c_str(), entry.c_str()) == 0)
+ return true;
+ }
+ return false;
+}
+
+std::string EnumListParameter::normaliseEntry(const std::string& entry) const
+{
+ for (const std::string& e : enums) {
+ if (strcasecmp(e.c_str(), entry.c_str()) == 0)
+ return e;
+ }
+ throw std::logic_error("Entry is not in list of valid values");
+}