From 884142c3ed4e452a888bfeb670088e033e4b4743 Mon Sep 17 00:00:00 2001 From: sdixon Date: Thu, 30 Jan 2025 00:02:27 +0000 Subject: [PATCH] updating use of config tomls in Client and Connection classes of the client2 module. client2/make_request_block and other files still need updating --- source/client2/client.cpp | 121 ++++++++------- source/client2/client.hpp | 10 +- source/client2/connection.cpp | 267 +++++++++++++++++----------------- source/client2/connection.hpp | 50 ++++++- source/config/config.cpp | 84 +++++++---- 5 files changed, 313 insertions(+), 219 deletions(-) diff --git a/source/client2/client.cpp b/source/client2/client.cpp index d36720b1..3ad4cc64 100644 --- a/source/client2/client.cpp +++ b/source/client2/client.cpp @@ -16,7 +16,7 @@ #include "clientserver/udaErrors.h" #include "clientserver/userid.h" #include "clientserver/xdrlib.h" -#include "logging/logging.h" +// #include "logging/logging.h" #include "uda/client.h" #include @@ -149,7 +149,8 @@ void update_client_block(ClientBlock& client_block, const uda::client::ClientFla } // namespace -uda::client::Client::Client() : version{ClientVersion}, _config{}, _connection{_config}, _protocol_version{ClientVersion} +uda::client::Client::Client() + :version{ClientVersion}, _config{}, _protocol_version{ClientVersion} { _host = DefaultHost; _port = DefaultPort; @@ -159,39 +160,52 @@ uda::client::Client::Client() : version{ClientVersion}, _config{}, _connection{_ _client_flags.alt_rank = 0; _client_flags.user_timeout = TimeOut; - // const char* timeout = getenv("UDA_TIMEOUT"); - // if (timeout != nullptr) { - // _client_flags.user_timeout = (int)strtol(getenv("UDA_TIMEOUT"), nullptr, 10); - // } - init_error_stack(); + _cache = uda::cache::open_cache(); + + char username[StringLength]; + user_id(username); + _client_username = username; + + init_client_block(&_client_block, ClientVersion, _client_username.c_str()); +} + +uda::client::Client::Client(std::string_view config_path) + : Client() +{ + // TODO: this will throw if the config path is illegal + // what behaviour do we want? ignore silently and fallback to default constructed client + // or let it crash here? + _config.load(config_path); + _connection = Connection(_config); - _config.load("uda-client.toml"); + // TODO: + // - timeout from config + // - parse client flags fromn config + // - initialise other any structs from config? //---------------------------------------------------------------- // Client set Property Flags (can be changed via property accessor functions) // Coded user properties changes have priority - try - { - auto client_flags = _config.get("client.flags"); - auto alt_rank = _config.get("client.alt_rank"); - - if (client_flags) { - _flags |= client_flags.as(); - } + auto client_flags = _config.get("client.flags"); + auto alt_rank = _config.get("client.alt_rank"); - if (alt_rank) { - _alt_rank = alt_rank.as(); - } + if (client_flags) { + _flags |= client_flags.as(); } - catch (const uda::config::ConfigError& e) - { - // do nothing, no config loaded - std::cout << "error loading config" << std::endl; - std::cout << e.what() << std::endl; + + if (alt_rank) { + _alt_rank = alt_rank.as(); } + // catch (const uda::config::ConfigError& e) + // { + // // do nothing, no config loaded + // std::cout << "error loading config" << std::endl; + // std::cout << e.what() << std::endl; + // } + //---------------------------------------------------------------- // X.509 Security Certification @@ -201,47 +215,39 @@ uda::client::Client::Client() : version{ClientVersion}, _config{}, _connection{_ // return(-1); // } - _cache = uda::cache::open_cache(); - - char username[StringLength]; - user_id(username); - _client_username = username; - - init_client_block(&_client_block, ClientVersion, _client_username.c_str()); - //---------------------------------------------------------------- // Check if Output Requested - try - { - constexpr int default_log_level = static_cast(UDA_LOG_NONE); - auto log_level = static_cast(_config.get("logging.level") - .as_or_default(default_log_level)); + constexpr int default_log_level = static_cast(UDA_LOG_NONE); + auto log_level = static_cast(_config.get("logging.level") + .as_or_default(default_log_level)); - if (log_level == UDA_LOG_NONE) { - return; - } - init_logging(); - set_log_level(log_level); - } - catch (const uda::config::ConfigError& e) - { - // no config loaded. Set logging to default: UDA_LOG_NONE - std::cout << "error loading config" << std::endl; - std::cout << e.what() << std::endl; + if (log_level == UDA_LOG_NONE) { return; } //--------------------------------------------------------------- // Open the Log File - errno = 0; - const auto log_dir = _config.get("logging.path").as_or_default(""s); const auto log_mode = _config.get("logging.mode").as_or_default("a"s); + initialise_logging(log_dir, log_level, log_mode); - auto file_name = (std::filesystem::path(log_dir) / "Debug.dbg").string(); + errno = 0; + if (errno == 0) + { + _config.print(); + } +} +void uda::client::Client::initialise_logging(const std::string& log_dir, LogLevel log_level, const std::string& log_mode) +{ + init_logging(); + set_log_level(log_level); + + errno = 0; + + auto file_name = (std::filesystem::path(log_dir) / "Debug.dbg").string(); set_log_file(UDA_LOG_WARN, file_name, log_mode); set_log_file(UDA_LOG_DEBUG, file_name, log_mode); set_log_file(UDA_LOG_INFO, file_name, log_mode); @@ -263,7 +269,6 @@ uda::client::Client::Client() : version{ClientVersion}, _config{}, _connection{_ return; } - _config.print(); } int uda::client::Client::fetch_meta() @@ -727,14 +732,18 @@ std::vector uda::client::Client::get(std::vector get(std::vector>& requests); @@ -120,7 +124,7 @@ class Client XDR* _client_input = nullptr; XDR* _client_output = nullptr; config::Config _config = {}; - Connection _connection; + Connection _connection = {}; HostList _host_list = {}; IoData _io_data = {}; bool _env_host = false; diff --git a/source/client2/connection.cpp b/source/client2/connection.cpp index 299666f0..c11d0187 100644 --- a/source/client2/connection.cpp +++ b/source/client2/connection.cpp @@ -66,6 +66,18 @@ using namespace uda::logging; using namespace std::string_literals; +uda::client::Connection::Connection(config::Config& config) + : _socket_list{} +{ + _port = config.get("connection.port").as_or_default(DefaultPort); + _host = config.get("connection.host").as_or_default(DefaultHost); + _max_socket_delay = config.get("connection.max_socket_delay").as_or_default(DefaultMaxSocketDelay); + _max_socket_attempts = config.get("connection.max_socket_attempts").as_or_default(DefaultMaxSocketAttempts); + _failover_port = config.get("connection.failover_port").as_or_default(0); + _failover_host = config.get("connection.failover_host").as_or_default(""s); +} + + int uda::client::Connection::open() { return _client_socket != -1; @@ -102,20 +114,16 @@ int uda::client::Connection::reconnect(XDR** client_input, XDR** client_output, // Instance a new server if the Client has changed the host and/or port number - auto server_reconnect = _config.get("client.server_reconnect").as_or_default(false); - auto server_change_socket = _config.get("client.server_change_socket").as_or_default(false); - - if (server_reconnect) { + if (_server_reconnect) { time(tv_server_start); // Start a New Server AGE timer _client_socket = -1; // Flags no Socket is open - _config.set("client.server_reconnect", false); + _server_reconnect = false; } // Client manages connections through the Socket id and specifies which running server to connect to - if (server_change_socket) { - auto server_socket = _config.get("client.server_socket").as_or_default(-1); - if ((socketId = find_socket(server_socket)) < 0) { + if (_server_change_socket) { + if ((socketId = find_socket(_client_socket)) < 0) { err = NO_SOCKET_CONNECTION; add_error(ErrorType::Code, __func__, err, "The User Specified Socket Connection does not exist"); return err; @@ -129,10 +137,13 @@ int uda::client::Connection::reconnect(XDR** client_input, XDR** client_output, *client_input = _socket_list[socketId].Input; *client_output = _socket_list[socketId].Output; - _config.set("client.server_change_socket", false); - _config.set("client.server_socket", _socket_list[socketId].fh); - _config.set("connection.port", _socket_list[socketId].port); - _config.set("connection.host", std::string{_socket_list[socketId].host}); + // _config.set("client.server_change_socket", false); + // _config.set("client.server_socket", _socket_list[socketId].fh); + // _config.set("connection.port", _socket_list[socketId].port); + // _config.set("connection.host", std::string{_socket_list[socketId].host}); + _server_change_socket = false; + _port = _socket_list[socketId].port; + _host = std::string{_socket_list[socketId].host}; } // save Previous data if a previous socket existed @@ -209,31 +220,87 @@ void setHints(struct addrinfo* hints, const char* host_name) } } -int uda::client::Connection::create(XDR* client_input, XDR* client_output, const HostList& host_list) +bool uda::client::Connection::reconnect_required() const { - int window_size = DBReadBlockSize; // 128K - int rc; + return _server_reconnect; +} - static int max_socket_delay = -1; - static int max_socket_attempts = -1; +int uda::client::Connection::get_port() const +{ + return _port; +} - if (max_socket_delay < 0) { - char* env = getenv("UDA_MAX_SOCKET_DELAY"); - if (env == nullptr) { - max_socket_delay = 10; - } else { - max_socket_delay = (int)strtol(env, nullptr, 10); - } - } +const std::string& uda::client::Connection::get_host() const +{ + return _host; +} - if (max_socket_attempts < 0) { - char* env = getenv("UDA_MAX_SOCKET_ATTEMPTS"); - if (env == nullptr) { - max_socket_attempts = 3; - } else { - max_socket_attempts = (int)strtol(env, nullptr, 10); +void uda::client::Connection::set_port(int port) +{ + if (port == _port) return; + _port = port; + _server_reconnect = true; +} + + +void uda::client::Connection::set_host_from_host_list(std::string_view host, const HostList& host_list) +{ + if (host == _host) return; + auto host_list_record = host_list.find_by_alias(host); + if (host_list_record != nullptr) + { + if (host_list_record->host_name != _host) + { + _host = host_list_record->host_name; + _server_reconnect = true; } + int port = host_list_record->port; + if (port > 0 && _port != port) + { + _port = port; + _server_reconnect = true; + } +#if defined(SSLAUTHENTICATION) && !defined(FATCLIENT) + putUdaClientSSLProtocol(host_list_record->isSSL); +#endif + } + else if ((host_list_record = host_list.find_by_name(host)) != nullptr) + { + _host = host; + _server_reconnect = true; + int port = host_list_record->port; + if (port > 0 && _port != port) + { + // Replace if found and different + _port = port; + } + } + else + { + // Does the host name contain the SSL protocol prefix? If so strip this off +#if defined(SSLAUTHENTICATION) && !defined(FATCLIENT) + if (boost::starts_with(host, "SSL://")) + { + auto new_host = host.substr(6); + if (new_host == _host) return + _host = new_host; + putUdaClientSSLProtocol(1); } + else + { + _host = host; + } +#else + _host = host; +#endif + _server_reconnect = true; + } +} + +int uda::client::Connection::create(XDR* client_input, XDR* client_output, const HostList& host_list) +{ + int window_size = DBReadBlockSize; // 128K + int rc; if (_client_socket >= 0) { // Check Already Opened? @@ -257,61 +324,17 @@ int uda::client::Connection::create(XDR* client_input, XDR* client_output, const // Identify the UDA server host and is the socket IPv4 or IPv6? - auto host_name = _config.get("connection.host").as_or_default(""s); char service_port[PORT_STRING]; - - // Check if the host_name is an alias for an IP address or domain name in the client configuration - replace if - // found - - auto server_host = _config.get("connection.host").as_or_default(""s); - auto server_port = _config.get("connection.port").as_or_default(0); - - auto host = host_list.find_by_alias(host_name); - if (host != nullptr) { - if (host->host_name != server_host) { - _config.set("connection.host", host->host_name); // Replace - } - int port = host->port; - if (port > 0 && server_port != port) { - _config.set("connection.port", port); - server_port = port; - } - } else if ((host = host_list.find_by_name(host_name)) != nullptr) { - // No alias found, maybe the domain name or ip address is listed - int port = host->port; - if (port > 0 && server_port != port) { - // Replace if found and different - _config.set("connection.port", port); - server_port = port; - } - } - snprintf(service_port, PORT_STRING, "%d", server_port); - - // Does the host name contain the SSL protocol prefix? If so strip this off - -#if defined(SSLAUTHENTICATION) && !defined(FATCLIENT) - if (boost::starts_with(host_name, "SSL://")) { - // Should be stripped already if via the HOST client configuration file - auto new_host = host_name.substr(6); - _config.set("connection.host", new_host); - putUdaClientSSLProtocol(1); - } else { - if (host != nullptr && host->isSSL) { - putUdaClientSSLProtocol(1); - } else { - putUdaClientSSLProtocol(0); - } - } -#endif + snprintf(service_port, PORT_STRING, "%d", _port); // Resolve the Host and the IP protocol to be used (Hints not used) struct addrinfo* result = nullptr; struct addrinfo hints = {0}; - setHints(&hints, host_name.c_str()); + setHints(&hints, _host.c_str()); errno = 0; - if ((rc = getaddrinfo(host_name.c_str(), service_port, &hints, &result)) != 0 || (errno != 0 && errno != ESRCH)) { + if ((rc = getaddrinfo(_host.c_str(), service_port, &hints, &result)) != 0 || (errno != 0 && errno != ESRCH)) { add_error(ErrorType::System, __func__, rc, (char*)gai_strerror(rc)); if (rc == EAI_SYSTEM || errno != 0) { add_error(ErrorType::System, __func__, errno, ""); @@ -361,30 +384,28 @@ int uda::client::Connection::create(XDR* client_input, XDR* client_output, const int ps; ps = getpid(); srand((unsigned int)ps); // Seed the random number generator with the process id - unsigned int delay = max_socket_delay > 0 ? (unsigned int)(rand() % max_socket_delay) : 0; // random delay + unsigned int delay = _max_socket_delay > 0 ? (unsigned int)(rand() % _max_socket_delay) : 0; // random delay sleep(delay); errno = 0; // wait period - for (int i = 0; i < max_socket_attempts; i++) { // try again + for (int i = 0; i < _max_socket_attempts; i++) { // try again while ((rc = connect(_client_socket, result->ai_addr, result->ai_addrlen)) && errno == EINTR) {} if (rc == 0 && errno == 0) { break; } - delay = max_socket_delay > 0 ? (unsigned int)(rand() % max_socket_delay) : 0; + delay = _max_socket_delay > 0 ? (unsigned int)(rand() % _max_socket_delay) : 0; sleep(delay); // wait period } if (rc != 0 || errno != 0) { UDA_LOG(UDA_LOG_DEBUG, "Connect errno = {}", errno); UDA_LOG(UDA_LOG_DEBUG, "Connect rc = {}", rc); - UDA_LOG(UDA_LOG_DEBUG, "Unable to connect to primary host: {} on port {}", host_name, service_port); + UDA_LOG(UDA_LOG_DEBUG, "Unable to connect to primary host: {} on port {}", _host, service_port); } - auto server_host2 = _config.get("client.server_host2").as_or_default(""s); - // Abandon the principal Host - attempt to connect to the secondary host - if (rc < 0 && !server_host2.empty() && server_host != server_host2) { + if (rc < 0 && !_failover_host.empty() && _host != _failover_host) { freeaddrinfo(result); result = nullptr; @@ -394,52 +415,36 @@ int uda::client::Connection::create(XDR* client_input, XDR* client_output, const ::closesocket(_client_socket); #endif _client_socket = -1; - host_name = server_host2; + // TODO: check this is correct intent: principal host becomes the failover host too + _host = _failover_host; // Check if the host_name is an alias for an IP address or name in the client configuration - replace if // found - auto server_port2 = _config.get("server_port2").as_or_default(0); - - host = host_list.find_by_alias(host_name); - if (host != nullptr) { - if (host->host_name != server_host2) { - server_host2 = host->host_name; - } - int port = host->port; - if (port > 0 && server_port2 != port) { - server_port2 = port; - } - } else if ((host = host_list.find_by_name(host_name)) != nullptr) { // No alias found - int port = host->port; - if (port > 0 && server_port2 != port) { - server_port2 = port; - } - } - snprintf(service_port, PORT_STRING, "%d", server_port2); - - // Does the host name contain the SSL protocol prefix? If so strip this off + snprintf(service_port, PORT_STRING, "%d", _failover_port); -#if defined(SSLAUTHENTICATION) && !defined(FATCLIENT) - if (boost::starts_with(host_name, "SSL://")) { - // Should be stripped already if via the HOST client configuration file - server_host2 = host_name.substr(6); - putUdaClientSSLProtocol(1); - } else { - if (host != nullptr && host->isSSL) { - putUdaClientSSLProtocol(1); - } else { - putUdaClientSSLProtocol(0); - } - } -#endif +// // Does the host name contain the SSL protocol prefix? If so strip this off +// +// #if defined(SSLAUTHENTICATION) && !defined(FATCLIENT) +// if (boost::starts_with(host_name, "SSL://")) { +// // Should be stripped already if via the HOST client configuration file +// _failover_host = host_name.substr(6); +// putUdaClientSSLProtocol(1); +// } else { +// if (host != nullptr && host->isSSL) { +// putUdaClientSSLProtocol(1); +// } else { +// putUdaClientSSLProtocol(0); +// } +// } +// #endif // Resolve the Host and the IP protocol to be used (Hints not used) - setHints(&hints, host_name.c_str()); + setHints(&hints, _host.c_str()); errno = 0; - if ((rc = getaddrinfo(host_name.c_str(), service_port, &hints, &result)) != 0 || errno != 0) { + if ((rc = getaddrinfo(_host.c_str(), service_port, &hints, &result)) != 0 || errno != 0) { add_error(ErrorType::System, __func__, rc, (char*)gai_strerror(rc)); if (rc == EAI_SYSTEM || errno != 0) { add_error(ErrorType::System, __func__, errno, ""); @@ -470,14 +475,14 @@ int uda::client::Connection::create(XDR* client_input, XDR* client_output, const return -1; } - for (int i = 0; i < max_socket_attempts; i++) { + for (int i = 0; i < _max_socket_attempts; i++) { while ((rc = connect(_client_socket, result->ai_addr, result->ai_addrlen)) && errno == EINTR) {} if (rc == 0) { - std::swap(server_port2, server_port); - std::swap(server_host2, server_host); + std::swap(_failover_port, _port); + std::swap(_failover_host, _host); break; } - delay = max_socket_delay > 0 ? (unsigned int)(rand() % max_socket_delay) : 0; + delay = _max_socket_delay > 0 ? (unsigned int)(rand() % _max_socket_delay) : 0; sleep(delay); // wait period } } @@ -538,8 +543,8 @@ int uda::client::Connection::create(XDR* client_input, XDR* client_output, const socket.type = SocketType::UDA; socket.status = 1; socket.fh = _client_socket; - socket.port = server_port; - strcpy(socket.host, server_host.c_str()); + socket.port = _port; + strcpy(socket.host, _host.c_str()); socket.tv_server_start = 0; socket.user_timeout = 0; socket.Input = client_input; @@ -547,9 +552,11 @@ int uda::client::Connection::create(XDR* client_input, XDR* client_output, const _socket_list.push_back(socket); - _config.set("client.server_reconnect", false); - _config.set("client.server_change_socket", false); - _config.set("client.server_socket", _client_socket); + // _config.set("client.server_reconnect", false); + // _config.set("client.server_change_socket", false); + // _config.set("client.server_socket", _client_socket); + _server_reconnect = false; + _server_change_socket = false; // Write the socket number to the SSL functions diff --git a/source/client2/connection.hpp b/source/client2/connection.hpp index 50ef8d88..e426bf21 100644 --- a/source/client2/connection.hpp +++ b/source/client2/connection.hpp @@ -10,6 +10,8 @@ #include "closedown.hpp" +#include + namespace uda::config { class Config; } @@ -17,31 +19,71 @@ class Config; namespace uda { namespace client { +constexpr auto DefaultHost = "localhost"; +constexpr auto DefaultPort = 56565; + +constexpr auto DefaultMaxSocketDelay = 10; +constexpr auto DefaultMaxSocketAttempts = 3; + class HostList; struct IoData { int* client_socket; }; +struct ConnectionState +{ + int max_socket_delay = DefaultMaxSocketDelay; + int max_socket_attempts = DefaultMaxSocketAttempts; + int port = DefaultPort; + int failover_port = 0; + std::string host = DefaultHost; + std::string failover_host {}; +}; + class Connection { public: - explicit Connection(config::Config& config) - : _config{config} - , _socket_list{} + Connection() + : _socket_list{} {} + explicit Connection(config::Config& config); int open(); int reconnect(XDR** client_input, XDR** client_output, time_t* tv_server_start, int* user_timeout); int create(XDR* client_input, XDR* client_output, const HostList& host_list); void close_down(ClosedownType type); IoData io_data() { return IoData{&_client_socket}; } + void set_port(int port); + void set_host_from_host_list(std::string_view host, const HostList& host_list); + + int get_port() const; + const std::string& get_host() const; + bool reconnect_required() const; + inline const ConnectionState get_state() const + { + return ConnectionState {_max_socket_delay, _max_socket_attempts, _port, _failover_port, + _host, _failover_host}; + } + + private: int _client_socket = -1; - config::Config& _config; std::vector _socket_list; // List of open sockets + int _port = DefaultPort; + std::string _host = DefaultHost; + int _max_socket_delay = DefaultMaxSocketDelay; + int _max_socket_attempts = DefaultMaxSocketAttempts; + + int _failover_port = 0; + std::string _failover_host = {}; + + mutable bool _server_reconnect = false; + mutable bool _server_change_socket = false; + int find_socket(int fh); void close_socket(int fh); + void unpack_config(); }; int writeout(void* iohandle, char* buf, int count); diff --git a/source/config/config.cpp b/source/config/config.cpp index 6ae8243b..cce99b06 100644 --- a/source/config/config.cpp +++ b/source/config/config.cpp @@ -140,36 +140,68 @@ class SectionValidator const std::vector Validators = { SectionValidator{"logging", - { - {"path", ValueType::String}, - {"mode", ValueType::Char}, - {"level", ValueType::Integer}, - }}, + { + {"path", ValueType::String}, + {"mode", ValueType::Char}, + {"level", ValueType::Integer}, + }}, SectionValidator{"plugins", - { - {"debug_single_file", ValueType::Boolean}, - {"directories", ValueType::String}, - {"metadata_plugin", ValueType::String}, - {"provenance_plugin", ValueType::String}, - }}, + { + {"debug_single_file", ValueType::Boolean}, + {"directories", ValueType::String}, + {"metadata_plugin", ValueType::String}, + {"provenance_plugin", ValueType::String}, + {"proxy_plugin", ValueType::String}, + }}, SectionValidator{"request", - { - {"delim", ValueType::String}, - {"default_device", ValueType::String}, - {"default_archive", ValueType::String}, - }}, + { + {"delim", ValueType::String}, + {"default_device", ValueType::String}, + {"default_archive", ValueType::String}, + }}, SectionValidator{"server", - { - {"is_proxy", ValueType::Boolean}, - {"proxy_target", ValueType::String}, - {"address", ValueType::String}, - {"port", ValueType::Integer}, - }}, + { + {"is_proxy", ValueType::Boolean}, + {"proxy_target", ValueType::String}, + {"address", ValueType::String}, + {"port", ValueType::Integer}, + {"startup_sleep", ValueType::Integer}, + {"private_path_target", ValueType::String}, + {"private_path_substitute", ValueType::String}, + {"default_format", ValueType::String}, + }}, SectionValidator{"connection", - { - {"host", ValueType::String}, - {"port", ValueType::Integer}, - }}, + { + {"host", ValueType::String}, + {"port", ValueType::Integer}, + {"max_socket_delay", ValueType::Integer}, + {"max_socket_attempts", ValueType::Integer}, + {"failover_host", ValueType::String}, + {"failover_port", ValueType::Integer}, + }}, + SectionValidator{"client_flags", + { + {"get_data_double", ValueType::Boolean}, + {"get_dim_double", ValueType::Boolean}, + {"get_time_double", ValueType::Boolean}, + {"get_bytes", ValueType::Boolean}, + {"get_bad", ValueType::Boolean}, + {"get_as_is", ValueType::Boolean}, + {"get_uncalibrated", ValueType::Boolean}, + {"get_not_offset", ValueType::Boolean}, + {"get_synthetic", ValueType::Boolean}, + {"get_scalar", ValueType::Boolean}, + {"get_no_dim_data", ValueType::Boolean}, + {"get_meta", ValueType::Boolean}, + {"timeout", ValueType::Integer}, + {"verbose", ValueType::Boolean}, + {"debug", ValueType::Boolean}, + {"alt_data", ValueType::Boolean}, + {"alt_rank", ValueType::Boolean}, + {"reuse_last_handle", ValueType::Boolean}, + {"free_and_reuse_last_handle", ValueType::Boolean}, + {"file_cache", ValueType::Boolean}, + }}, }; void validate(toml::table& table)