diff options
Diffstat (limited to 'src/common/config.cc')
-rw-r--r-- | src/common/config.cc | 1577 |
1 files changed, 1577 insertions, 0 deletions
diff --git a/src/common/config.cc b/src/common/config.cc new file mode 100644 index 000000000..c8101587b --- /dev/null +++ b/src/common/config.cc @@ -0,0 +1,1577 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab +/* + * Ceph - scalable distributed file system + * + * Copyright (C) 2004-2006 Sage Weil <sage@newdream.net> + * + * This is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License version 2.1, as published by the Free Software + * Foundation. See file COPYING. + * + */ + +#include <filesystem> +#include "common/ceph_argparse.h" +#include "common/common_init.h" +#include "common/config.h" +#include "common/config_obs.h" +#include "include/str_list.h" +#include "include/stringify.h" +#include "osd/osd_types.h" +#include "common/errno.h" +#include "common/hostname.h" +#include "common/dout.h" + +/* Don't use standard Ceph logging in this file. + * We can't use logging until it's initialized, and a lot of the necessary + * initialization happens here. + */ +#undef dout +#undef pdout +#undef derr +#undef generic_dout + +// set set_mon_vals() +#define dout_subsys ceph_subsys_monc + +namespace fs = std::filesystem; + +using std::cerr; +using std::cout; +using std::map; +using std::less; +using std::list; +using std::ostream; +using std::ostringstream; +using std::pair; +using std::string; +using std::string_view; +using std::vector; + +using ceph::bufferlist; +using ceph::decode; +using ceph::encode; +using ceph::Formatter; + +static const char *CEPH_CONF_FILE_DEFAULT = "$data_dir/config,/etc/ceph/$cluster.conf,$home/.ceph/$cluster.conf,$cluster.conf" +#if defined(__FreeBSD__) + ",/usr/local/etc/ceph/$cluster.conf" +#elif defined(_WIN32) + ",$programdata/ceph/$cluster.conf" +#endif + ; + +#define _STR(x) #x +#define STRINGIFY(x) _STR(x) + +const char *ceph_conf_level_name(int level) +{ + switch (level) { + case CONF_DEFAULT: return "default"; // built-in default + case CONF_MON: return "mon"; // monitor config database + case CONF_ENV: return "env"; // process environment (CEPH_ARGS) + case CONF_FILE: return "file"; // ceph.conf file + case CONF_CMDLINE: return "cmdline"; // process command line args + case CONF_OVERRIDE: return "override"; // injectargs or 'config set' at runtime + case CONF_FINAL: return "final"; + default: return "???"; + } +} + +int ceph_resolve_file_search(const std::string& filename_list, + std::string& result) +{ + list<string> ls; + get_str_list(filename_list, ";,", ls); + + int ret = -ENOENT; + list<string>::iterator iter; + for (iter = ls.begin(); iter != ls.end(); ++iter) { + int fd = ::open(iter->c_str(), O_RDONLY|O_CLOEXEC); + if (fd < 0) { + ret = -errno; + continue; + } + close(fd); + result = *iter; + return 0; + } + + return ret; +} + +static int conf_stringify(const Option::value_t& v, string *out) +{ + if (v == Option::value_t{}) { + return -ENOENT; + } + *out = Option::to_str(v); + return 0; +} + +md_config_t::md_config_t(ConfigValues& values, + const ConfigTracker& tracker, + bool is_daemon) + : is_daemon(is_daemon) +{ + // Load the compile-time list of Option into + // a map so that we can resolve keys quickly. + for (const auto &i : ceph_options) { + if (schema.count(i.name)) { + // We may be instantiated pre-logging so send + std::cerr << "Duplicate config key in schema: '" << i.name << "'" + << std::endl; + ceph_abort(); + } + schema.emplace(i.name, i); + } + + // Define the debug_* options as well. + subsys_options.reserve(values.subsys.get_num()); + for (unsigned i = 0; i < values.subsys.get_num(); ++i) { + string name = string("debug_") + values.subsys.get_name(i); + subsys_options.push_back( + Option(name, Option::TYPE_STR, Option::LEVEL_ADVANCED)); + Option& opt = subsys_options.back(); + opt.set_default(stringify(values.subsys.get_log_level(i)) + "/" + + stringify(values.subsys.get_gather_level(i))); + string desc = string("Debug level for ") + values.subsys.get_name(i); + opt.set_description(desc.c_str()); + opt.set_flag(Option::FLAG_RUNTIME); + opt.set_long_description("The value takes the form 'N' or 'N/M' where N and M are values between 0 and 99. N is the debug level to log (all values below this are included), and M is the level to gather and buffer in memory. In the event of a crash, the most recent items <= M are dumped to the log file."); + opt.set_subsys(i); + opt.set_validator([](std::string *value, std::string *error_message) { + int m, n; + int r = sscanf(value->c_str(), "%d/%d", &m, &n); + if (r >= 1) { + if (m < 0 || m > 99) { + *error_message = "value must be in range [0, 99]"; + return -ERANGE; + } + if (r == 2) { + if (n < 0 || n > 99) { + *error_message = "value must be in range [0, 99]"; + return -ERANGE; + } + } else { + // normalize to M/N + n = m; + *value = stringify(m) + "/" + stringify(n); + } + } else { + *error_message = "value must take the form N or N/M, where N and M are integers"; + return -EINVAL; + } + return 0; + }); + } + for (auto& opt : subsys_options) { + schema.emplace(opt.name, opt); + } + + // Populate list of legacy_values according to the OPTION() definitions + // Note that this is just setting up our map of name->member ptr. The + // default values etc will get loaded in along with new-style data, + // as all loads write to both the values map, and the legacy + // members if present. + legacy_values = { +#define OPTION(name, type) \ + {STRINGIFY(name), &ConfigValues::name}, +#define SAFE_OPTION(name, type) OPTION(name, type) +#include "options/legacy_config_opts.h" +#undef OPTION +#undef SAFE_OPTION + }; + + validate_schema(); + + // Validate default values from the schema + for (const auto &i : schema) { + const Option &opt = i.second; + if (opt.type == Option::TYPE_STR) { + bool has_daemon_default = (opt.daemon_value != Option::value_t{}); + Option::value_t default_val; + if (is_daemon && has_daemon_default) { + default_val = opt.daemon_value; + } else { + default_val = opt.value; + } + // We call pre_validate as a sanity check, but also to get any + // side effect (value modification) from the validator. + auto* def_str = std::get_if<std::string>(&default_val); + std::string val = *def_str; + std::string err; + if (opt.pre_validate(&val, &err) != 0) { + std::cerr << "Default value " << opt.name << "=" << *def_str << " is " + "invalid: " << err << std::endl; + + // This is the compiled-in default that is failing its own option's + // validation, so this is super-invalid and should never make it + // past a pull request: crash out. + ceph_abort(); + } + if (val != *def_str) { + // if the validator normalizes the string into a different form than + // what was compiled in, use that. + set_val_default(values, tracker, opt.name, val); + } + } + } + + // Copy out values (defaults) into any legacy (C struct member) fields + update_legacy_vals(values); +} + +md_config_t::~md_config_t() +{ +} + +/** + * Sanity check schema. Assert out on failures, to ensure any bad changes + * cannot possibly pass any testing and make it into a release. + */ +void md_config_t::validate_schema() +{ + for (const auto &i : schema) { + const auto &opt = i.second; + for (const auto &see_also_key : opt.see_also) { + if (schema.count(see_also_key) == 0) { + std::cerr << "Non-existent see-also key '" << see_also_key + << "' on option '" << opt.name << "'" << std::endl; + ceph_abort(); + } + } + } + + for (const auto &i : legacy_values) { + if (schema.count(i.first) == 0) { + std::cerr << "Schema is missing legacy field '" << i.first << "'" + << std::endl; + ceph_abort(); + } + } +} + +const Option *md_config_t::find_option(const std::string_view name) const +{ + auto p = schema.find(name); + if (p != schema.end()) { + return &p->second; + } + return nullptr; +} + +void md_config_t::set_val_default(ConfigValues& values, + const ConfigTracker& tracker, + const string_view name, const std::string& val) +{ + const Option *o = find_option(name); + ceph_assert(o); + string err; + int r = _set_val(values, tracker, val, *o, CONF_DEFAULT, &err); + ceph_assert(r >= 0); +} + +int md_config_t::set_mon_vals(CephContext *cct, + ConfigValues& values, + const ConfigTracker& tracker, + const map<string,string,less<>>& kv, + config_callback config_cb) +{ + ignored_mon_values.clear(); + + if (!config_cb) { + ldout(cct, 4) << __func__ << " no callback set" << dendl; + } + + for (auto& i : kv) { + if (config_cb) { + if (config_cb(i.first, i.second)) { + ldout(cct, 4) << __func__ << " callback consumed " << i.first << dendl; + continue; + } + ldout(cct, 4) << __func__ << " callback ignored " << i.first << dendl; + } + const Option *o = find_option(i.first); + if (!o) { + ldout(cct,10) << __func__ << " " << i.first << " = " << i.second + << " (unrecognized option)" << dendl; + continue; + } + if (o->has_flag(Option::FLAG_NO_MON_UPDATE)) { + ignored_mon_values.emplace(i); + continue; + } + std::string err; + int r = _set_val(values, tracker, i.second, *o, CONF_MON, &err); + if (r < 0) { + ldout(cct, 4) << __func__ << " failed to set " << i.first << " = " + << i.second << ": " << err << dendl; + ignored_mon_values.emplace(i); + } else if (r == ConfigValues::SET_NO_CHANGE || + r == ConfigValues::SET_NO_EFFECT) { + ldout(cct,20) << __func__ << " " << i.first << " = " << i.second + << " (no change)" << dendl; + } else if (r == ConfigValues::SET_HAVE_EFFECT) { + ldout(cct,10) << __func__ << " " << i.first << " = " << i.second << dendl; + } else { + ceph_abort(); + } + } + values.for_each([&] (auto name, auto configs) { + auto config = configs.find(CONF_MON); + if (config == configs.end()) { + return; + } + if (kv.find(name) != kv.end()) { + return; + } + ldout(cct,10) << __func__ << " " << name + << " cleared (was " << Option::to_str(config->second) << ")" + << dendl; + values.rm_val(name, CONF_MON); + // if this is a debug option, it needs to propagate to teh subsys; + // this isn't covered by update_legacy_vals() below. similarly, + // we want to trigger a config notification for these items. + const Option *o = find_option(name); + _refresh(values, *o); + }); + values_bl.clear(); + update_legacy_vals(values); + return 0; +} + +int md_config_t::parse_config_files(ConfigValues& values, + const ConfigTracker& tracker, + const char *conf_files_str, + std::ostream *warnings, + int flags) +{ + if (safe_to_start_threads) + return -ENOSYS; + + if (values.cluster.empty() && !conf_files_str) { + values.cluster = get_cluster_name(nullptr); + } + // open new conf + for (auto& fn : get_conffile_paths(values, conf_files_str, warnings, flags)) { + bufferlist bl; + std::string error; + if (bl.read_file(fn.c_str(), &error)) { + parse_error = error; + continue; + } + ostringstream oss; + int ret = parse_buffer(values, tracker, bl.c_str(), bl.length(), &oss); + if (ret == 0) { + parse_error.clear(); + conf_path = fn; + break; + } + parse_error = oss.str(); + if (ret != -ENOENT) { + return ret; + } + } + // it must have been all ENOENTs, that's the only way we got here + if (conf_path.empty()) { + return -ENOENT; + } + if (values.cluster.empty()) { + values.cluster = get_cluster_name(conf_path.c_str()); + } + update_legacy_vals(values); + return 0; +} + +int +md_config_t::parse_buffer(ConfigValues& values, + const ConfigTracker& tracker, + const char* buf, size_t len, + std::ostream* warnings) +{ + if (!cf.parse_buffer(string_view{buf, len}, warnings)) { + return -EINVAL; + } + const auto my_sections = get_my_sections(values); + for (const auto &i : schema) { + const auto &opt = i.second; + std::string val; + if (_get_val_from_conf_file(my_sections, opt.name, val)) { + continue; + } + std::string error_message; + if (_set_val(values, tracker, val, opt, CONF_FILE, &error_message) < 0) { + if (warnings != nullptr) { + *warnings << "parse error setting " << std::quoted(opt.name) + << " to " << std::quoted(val); + if (!error_message.empty()) { + *warnings << " (" << error_message << ")"; + } + *warnings << '\n'; + } + } + } + cf.check_old_style_section_names({"mds", "mon", "osd"}, cerr); + return 0; +} + +std::list<std::string> +md_config_t::get_conffile_paths(const ConfigValues& values, + const char *conf_files_str, + std::ostream *warnings, + int flags) const +{ + if (!conf_files_str) { + const char *c = getenv("CEPH_CONF"); + if (c) { + conf_files_str = c; + } else { + if (flags & CINIT_FLAG_NO_DEFAULT_CONFIG_FILE) + return {}; + conf_files_str = CEPH_CONF_FILE_DEFAULT; + } + } + + std::list<std::string> paths; + get_str_list(conf_files_str, ";,", paths); + for (auto i = paths.begin(); i != paths.end(); ) { + string& path = *i; + if (path.find("$data_dir") != path.npos && + data_dir_option.empty()) { + // useless $data_dir item, skip + i = paths.erase(i); + } else { + early_expand_meta(values, path, warnings); + ++i; + } + } + return paths; +} + +std::string md_config_t::get_cluster_name(const char* conffile) +{ + if (conffile) { + // If cluster name is not set yet, use the prefix of the + // basename of configuration file as cluster name. + if (fs::path path{conffile}; path.extension() == ".conf") { + return path.stem().string(); + } else { + // If the configuration file does not follow $cluster.conf + // convention, we do the last try and assign the cluster to + // 'ceph'. + return "ceph"; + } + } else { + // set the cluster name to 'ceph' when configuration file is not specified. + return "ceph"; + } +} + +void md_config_t::parse_env(unsigned entity_type, + ConfigValues& values, + const ConfigTracker& tracker, + const char *args_var) +{ + if (safe_to_start_threads) + return; + if (!args_var) { + args_var = "CEPH_ARGS"; + } + if (auto s = getenv("CEPH_KEYRING"); s) { + string err; + _set_val(values, tracker, s, *find_option("keyring"), CONF_ENV, &err); + } + if (auto dir = getenv("CEPH_LIB"); dir) { + for (auto name : { "erasure_code_dir", "plugin_dir", "osd_class_dir" }) { + std::string err; + const Option *o = find_option(name); + ceph_assert(o); + _set_val(values, tracker, dir, *o, CONF_ENV, &err); + } + } + + // Apply pod memory limits: + // + // There are two types of resource requests: `limits` and `requests`. + // + // - Requests: Used by the K8s scheduler to determine on which nodes to + // schedule the pods. This helps spread the pods to different nodes. This + // value should be conservative in order to make sure all the pods are + // schedulable. This corresponds to POD_MEMORY_REQUEST (set by the Rook + // CRD) and is the target memory utilization we try to maintain for daemons + // that respect it. + // + // If POD_MEMORY_REQUEST is present, we use it as the target. + // + // - Limits: At runtime, the container runtime (and Linux) will use the + // limits to see if the pod is using too many resources. In that case, the + // pod will be killed/restarted automatically if the pod goes over the limit. + // This should be higher than what is specified for requests (potentially + // much higher). This corresponds to the cgroup memory limit that will + // trigger the Linux OOM killer. + // + // If POD_MEMORY_LIMIT is present, we use it as the /default/ value for + // the target, which means it will only apply if the *_memory_target option + // isn't set via some other path (e.g., POD_MEMORY_REQUEST, or the cluster + // config, or whatever.) + // + // Here are the documented best practices: + // https://kubernetes.io/docs/tasks/configure-pod-container/assign-cpu-resource/#motivation-for-cpu-requests-and-limits + // + // When the operator creates the CephCluster CR, it will need to generate the + // desired requests and limits. As long as we are conservative in our choice + // for requests and generous with the limits we should be in a good place to + // get started. + // + // The support in Rook is already there for applying the limits as seen in + // these links. + // + // Rook docs on the resource requests and limits: + // https://rook.io/docs/rook/v1.0/ceph-cluster-crd.html#cluster-wide-resources-configuration-settings + // Example CR settings: + // https://github.com/rook/rook/blob/6d2ef936698593036185aabcb00d1d74f9c7bfc1/cluster/examples/kubernetes/ceph/cluster.yaml#L90 + // + uint64_t pod_limit = 0, pod_request = 0; + if (auto pod_lim = getenv("POD_MEMORY_LIMIT"); pod_lim) { + string err; + uint64_t v = atoll(pod_lim); + if (v) { + switch (entity_type) { + case CEPH_ENTITY_TYPE_OSD: + { + double cgroup_ratio = get_val<double>( + values, "osd_memory_target_cgroup_limit_ratio"); + if (cgroup_ratio > 0.0) { + pod_limit = v * cgroup_ratio; + // set osd_memory_target *default* based on cgroup limit, so that + // it can be overridden by any explicit settings elsewhere. + set_val_default(values, tracker, + "osd_memory_target", stringify(pod_limit)); + } + } + } + } + } + if (auto pod_req = getenv("POD_MEMORY_REQUEST"); pod_req) { + if (uint64_t v = atoll(pod_req); v) { + pod_request = v; + } + } + if (pod_request && pod_limit) { + // If both LIMIT and REQUEST are set, ensure that we use the + // min of request and limit*ratio. This is important + // because k8s set set LIMIT == REQUEST if only LIMIT is + // specified, and we want to apply the ratio in that case, + // even though REQUEST is present. + pod_request = std::min<uint64_t>(pod_request, pod_limit); + } + if (pod_request) { + string err; + switch (entity_type) { + case CEPH_ENTITY_TYPE_OSD: + _set_val(values, tracker, stringify(pod_request), + *find_option("osd_memory_target"), + CONF_ENV, &err); + break; + } + } + + if (getenv(args_var)) { + vector<const char *> env_args; + env_to_vec(env_args, args_var); + parse_argv(values, tracker, env_args, CONF_ENV); + } +} + +void md_config_t::show_config(const ConfigValues& values, + std::ostream& out) const +{ + _show_config(values, &out, nullptr); +} + +void md_config_t::show_config(const ConfigValues& values, + Formatter *f) const +{ + _show_config(values, nullptr, f); +} + +void md_config_t::config_options(Formatter *f) const +{ + f->open_array_section("options"); + for (const auto& i: schema) { + f->dump_object("option", i.second); + } + f->close_section(); +} + +void md_config_t::_show_config(const ConfigValues& values, + std::ostream *out, Formatter *f) const +{ + if (out) { + *out << "name = " << values.name << std::endl; + *out << "cluster = " << values.cluster << std::endl; + } + if (f) { + f->dump_string("name", stringify(values.name)); + f->dump_string("cluster", values.cluster); + } + for (const auto& i: schema) { + const Option &opt = i.second; + string val; + conf_stringify(_get_val(values, opt), &val); + if (out) { + *out << opt.name << " = " << val << std::endl; + } + if (f) { + f->dump_string(opt.name.c_str(), val); + } + } +} + +int md_config_t::parse_argv(ConfigValues& values, + const ConfigTracker& tracker, + std::vector<const char*>& args, int level) +{ + if (safe_to_start_threads) { + return -ENOSYS; + } + + // In this function, don't change any parts of the configuration directly. + // Instead, use set_val to set them. This will allow us to send the proper + // observer notifications later. + std::string val; + for (std::vector<const char*>::iterator i = args.begin(); i != args.end(); ) { + if (strcmp(*i, "--") == 0) { + /* Normally we would use ceph_argparse_double_dash. However, in this + * function we *don't* want to remove the double dash, because later + * argument parses will still need to see it. */ + break; + } + else if (ceph_argparse_flag(args, i, "--show_conf", (char*)NULL)) { + cerr << cf << std::endl; + _exit(0); + } + else if (ceph_argparse_flag(args, i, "--show_config", (char*)NULL)) { + do_show_config = true; + } + else if (ceph_argparse_witharg(args, i, &val, "--show_config_value", (char*)NULL)) { + do_show_config_value = val; + } + else if (ceph_argparse_flag(args, i, "--no-mon-config", (char*)NULL)) { + values.no_mon_config = true; + } + else if (ceph_argparse_flag(args, i, "--mon-config", (char*)NULL)) { + values.no_mon_config = false; + } + else if (ceph_argparse_flag(args, i, "--foreground", "-f", (char*)NULL)) { + set_val_or_die(values, tracker, "daemonize", "false"); + } + else if (ceph_argparse_flag(args, i, "-d", (char*)NULL)) { + set_val_or_die(values, tracker, "fuse_debug", "true"); + set_val_or_die(values, tracker, "daemonize", "false"); + set_val_or_die(values, tracker, "log_file", ""); + set_val_or_die(values, tracker, "log_to_stderr", "true"); + set_val_or_die(values, tracker, "err_to_stderr", "true"); + set_val_or_die(values, tracker, "log_to_syslog", "false"); + } + // Some stuff that we wanted to give universal single-character options for + // Careful: you can burn through the alphabet pretty quickly by adding + // to this list. + else if (ceph_argparse_witharg(args, i, &val, "--monmap", "-M", (char*)NULL)) { + set_val_or_die(values, tracker, "monmap", val.c_str()); + } + else if (ceph_argparse_witharg(args, i, &val, "--mon_host", "-m", (char*)NULL)) { + set_val_or_die(values, tracker, "mon_host", val.c_str()); + } + else if (ceph_argparse_witharg(args, i, &val, "--bind", (char*)NULL)) { + set_val_or_die(values, tracker, "public_addr", val.c_str()); + } + else if (ceph_argparse_witharg(args, i, &val, "--keyfile", "-K", (char*)NULL)) { + bufferlist bl; + string err; + int r; + if (val == "-") { + r = bl.read_fd(STDIN_FILENO, 1024); + } else { + r = bl.read_file(val.c_str(), &err); + } + if (r >= 0) { + string k(bl.c_str(), bl.length()); + set_val_or_die(values, tracker, "key", k.c_str()); + } + } + else if (ceph_argparse_witharg(args, i, &val, "--keyring", "-k", (char*)NULL)) { + set_val_or_die(values, tracker, "keyring", val.c_str()); + } + else if (ceph_argparse_witharg(args, i, &val, "--client_mountpoint", "-r", (char*)NULL)) { + set_val_or_die(values, tracker, "client_mountpoint", val.c_str()); + } + else { + int r = parse_option(values, tracker, args, i, NULL, level); + if (r < 0) { + return r; + } + } + } + // meta expands could have modified anything. Copy it all out again. + update_legacy_vals(values); + return 0; +} + +void md_config_t::do_argv_commands(const ConfigValues& values) const +{ + + if (do_show_config) { + _show_config(values, &cout, NULL); + _exit(0); + } + + if (do_show_config_value.size()) { + string val; + int r = conf_stringify(_get_val(values, do_show_config_value, 0, &cerr), + &val); + if (r < 0) { + if (r == -ENOENT) + std::cerr << "failed to get config option '" + << do_show_config_value << "': option not found" << std::endl; + else + std::cerr << "failed to get config option '" + << do_show_config_value << "': " << cpp_strerror(r) + << std::endl; + _exit(1); + } + std::cout << val << std::endl; + _exit(0); + } +} + +int md_config_t::parse_option(ConfigValues& values, + const ConfigTracker& tracker, + std::vector<const char*>& args, + std::vector<const char*>::iterator& i, + ostream *oss, + int level) +{ + int ret = 0; + size_t o = 0; + std::string val; + + std::string option_name; + std::string error_message; + o = 0; + for (const auto& opt_iter: schema) { + const Option &opt = opt_iter.second; + ostringstream err; + std::string as_option("--"); + as_option += opt.name; + option_name = opt.name; + if (ceph_argparse_witharg( + args, i, &val, err, + string(string("--default-") + opt.name).c_str(), (char*)NULL)) { + if (!err.str().empty()) { + error_message = err.str(); + ret = -EINVAL; + break; + } + ret = _set_val(values, tracker, val, opt, CONF_DEFAULT, &error_message); + break; + } else if (opt.type == Option::TYPE_BOOL) { + int res; + if (ceph_argparse_binary_flag(args, i, &res, oss, as_option.c_str(), + (char*)NULL)) { + if (res == 0) + ret = _set_val(values, tracker, "false", opt, level, &error_message); + else if (res == 1) + ret = _set_val(values, tracker, "true", opt, level, &error_message); + else + ret = res; + break; + } else { + std::string no("--no-"); + no += opt.name; + if (ceph_argparse_flag(args, i, no.c_str(), (char*)NULL)) { + ret = _set_val(values, tracker, "false", opt, level, &error_message); + break; + } + } + } else if (ceph_argparse_witharg(args, i, &val, err, + as_option.c_str(), (char*)NULL)) { + if (!err.str().empty()) { + error_message = err.str(); + ret = -EINVAL; + break; + } + ret = _set_val(values, tracker, val, opt, level, &error_message); + break; + } + ++o; + } + + if (ret < 0 || !error_message.empty()) { + ceph_assert(!option_name.empty()); + if (oss) { + *oss << "Parse error setting " << option_name << " to '" + << val << "' using injectargs"; + if (!error_message.empty()) { + *oss << " (" << error_message << ")"; + } + *oss << ".\n"; + } else { + cerr << "parse error setting '" << option_name << "' to '" + << val << "'"; + if (!error_message.empty()) { + cerr << " (" << error_message << ")"; + } + cerr << "\n" << std::endl; + } + } + + if (o == schema.size()) { + // ignore + ++i; + } + return ret >= 0 ? 0 : ret; +} + +int md_config_t::parse_injectargs(ConfigValues& values, + const ConfigTracker& tracker, + std::vector<const char*>& args, + std::ostream *oss) +{ + int ret = 0; + for (std::vector<const char*>::iterator i = args.begin(); i != args.end(); ) { + int r = parse_option(values, tracker, args, i, oss, CONF_OVERRIDE); + if (r < 0) + ret = r; + } + return ret; +} + +void md_config_t::set_safe_to_start_threads() +{ + safe_to_start_threads = true; +} + +void md_config_t::_clear_safe_to_start_threads() +{ + safe_to_start_threads = false; +} + +int md_config_t::injectargs(ConfigValues& values, + const ConfigTracker& tracker, + const std::string& s, std::ostream *oss) +{ + int ret; + char b[s.length()+1]; + strcpy(b, s.c_str()); + std::vector<const char*> nargs; + char *p = b; + while (*p) { + nargs.push_back(p); + while (*p && *p != ' ') p++; + if (!*p) + break; + *p++ = 0; + while (*p && *p == ' ') p++; + } + ret = parse_injectargs(values, tracker, nargs, oss); + if (!nargs.empty()) { + *oss << " failed to parse arguments: "; + std::string prefix; + for (std::vector<const char*>::const_iterator i = nargs.begin(); + i != nargs.end(); ++i) { + *oss << prefix << *i; + prefix = ","; + } + *oss << "\n"; + ret = -EINVAL; + } + update_legacy_vals(values); + return ret; +} + +void md_config_t::set_val_or_die(ConfigValues& values, + const ConfigTracker& tracker, + const std::string_view key, + const std::string &val) +{ + std::stringstream err; + int ret = set_val(values, tracker, key, val, &err); + if (ret != 0) { + std::cerr << "set_val_or_die(" << key << "): " << err.str(); + } + ceph_assert(ret == 0); +} + +int md_config_t::set_val(ConfigValues& values, + const ConfigTracker& tracker, + const std::string_view key, const char *val, + std::stringstream *err_ss) +{ + if (key.empty()) { + if (err_ss) *err_ss << "No key specified"; + return -EINVAL; + } + if (!val) { + return -EINVAL; + } + + std::string v(val); + + string k(ConfFile::normalize_key_name(key)); + + const auto &opt_iter = schema.find(k); + if (opt_iter != schema.end()) { + const Option &opt = opt_iter->second; + std::string error_message; + int r = _set_val(values, tracker, v, opt, CONF_OVERRIDE, &error_message); + if (r >= 0) { + if (err_ss) *err_ss << "Set " << opt.name << " to " << v; + r = 0; + } else { + if (err_ss) *err_ss << error_message; + } + return r; + } + + if (err_ss) *err_ss << "Configuration option not found: '" << key << "'"; + return -ENOENT; +} + +int md_config_t::rm_val(ConfigValues& values, const std::string_view key) +{ + return _rm_val(values, key, CONF_OVERRIDE); +} + +void md_config_t::get_defaults_bl(const ConfigValues& values, + bufferlist *bl) +{ + if (defaults_bl.length() == 0) { + uint32_t n = 0; + bufferlist bl; + for (const auto &i : schema) { + ++n; + encode(i.second.name, bl); + auto [value, found] = values.get_value(i.second.name, CONF_DEFAULT); + if (found) { + encode(Option::to_str(value), bl); + } else { + string val; + conf_stringify(_get_val_default(i.second), &val); + encode(val, bl); + } + } + encode(n, defaults_bl); + defaults_bl.claim_append(bl); + } + *bl = defaults_bl; +} + +void md_config_t::get_config_bl( + const ConfigValues& values, + uint64_t have_version, + bufferlist *bl, + uint64_t *got_version) +{ + if (values_bl.length() == 0) { + uint32_t n = 0; + bufferlist bl; + values.for_each([&](auto& name, auto& configs) { + if (name == "fsid" || + name == "host") { + return; + } + ++n; + encode(name, bl); + encode((uint32_t)configs.size(), bl); + for (auto& j : configs) { + encode(j.first, bl); + encode(Option::to_str(j.second), bl); + } + }); + // make sure overridden items appear, and include the default value + for (auto& i : ignored_mon_values) { + if (values.contains(i.first)) { + continue; + } + if (i.first == "fsid" || + i.first == "host") { + continue; + } + const Option *opt = find_option(i.first); + if (!opt) { + continue; + } + ++n; + encode(i.first, bl); + encode((uint32_t)1, bl); + encode((int32_t)CONF_DEFAULT, bl); + string val; + conf_stringify(_get_val_default(*opt), &val); + encode(val, bl); + } + encode(n, values_bl); + values_bl.claim_append(bl); + encode(ignored_mon_values, values_bl); + ++values_bl_version; + } + if (have_version != values_bl_version) { + *bl = values_bl; + *got_version = values_bl_version; + } +} + +std::optional<std::string> md_config_t::get_val_default(std::string_view key) +{ + std::string val; + const Option *opt = find_option(key); + if (opt && (conf_stringify(_get_val_default(*opt), &val) == 0)) { + return std::make_optional(std::move(val)); + } + return std::nullopt; +} + +int md_config_t::get_val(const ConfigValues& values, + const std::string_view key, char **buf, int len) const +{ + string k(ConfFile::normalize_key_name(key)); + return _get_val_cstr(values, k, buf, len); +} + +int md_config_t::get_val( + const ConfigValues& values, + const std::string_view key, + std::string *val) const +{ + return conf_stringify(get_val_generic(values, key), val); +} + +Option::value_t md_config_t::get_val_generic( + const ConfigValues& values, + const std::string_view key) const +{ + return _get_val(values, key); +} + +Option::value_t md_config_t::_get_val( + const ConfigValues& values, + const std::string_view key, + expand_stack_t *stack, + std::ostream *err) const +{ + if (key.empty()) { + return {}; + } + + // In key names, leading and trailing whitespace are not significant. + string k(ConfFile::normalize_key_name(key)); + + const Option *o = find_option(k); + if (!o) { + // not a valid config option + return {}; + } + + return _get_val(values, *o, stack, err); +} + +Option::value_t md_config_t::_get_val( + const ConfigValues& values, + const Option& o, + expand_stack_t *stack, + std::ostream *err) const +{ + expand_stack_t a_stack; + if (!stack) { + stack = &a_stack; + } + return _expand_meta(values, + _get_val_nometa(values, o), + &o, stack, err); +} + +Option::value_t md_config_t::_get_val_nometa(const ConfigValues& values, + const Option& o) const +{ + if (auto [value, found] = values.get_value(o.name, -1); found) { + return value; + } else { + return _get_val_default(o); + } +} + +const Option::value_t& md_config_t::_get_val_default(const Option& o) const +{ + bool has_daemon_default = (o.daemon_value != Option::value_t{}); + if (is_daemon && has_daemon_default) { + return o.daemon_value; + } else { + return o.value; + } +} + +void md_config_t::early_expand_meta( + const ConfigValues& values, + std::string &val, + std::ostream *err) const +{ + expand_stack_t stack; + Option::value_t v = _expand_meta(values, + Option::value_t(val), + nullptr, &stack, err); + conf_stringify(v, &val); +} + +bool md_config_t::finalize_reexpand_meta(ConfigValues& values, + const ConfigTracker& tracker) +{ + std::vector<std::string> reexpands; + reexpands.swap(may_reexpand_meta); + for (auto& name : reexpands) { + // always refresh the options if they are in the may_reexpand_meta + // map, because the options may have already been expanded with old + // meta. + const auto &opt_iter = schema.find(name); + ceph_assert(opt_iter != schema.end()); + const Option &opt = opt_iter->second; + _refresh(values, opt); + } + + return !may_reexpand_meta.empty(); +} + +Option::value_t md_config_t::_expand_meta( + const ConfigValues& values, + const Option::value_t& in, + const Option *o, + expand_stack_t *stack, + std::ostream *err) const +{ + //cout << __func__ << " in '" << in << "' stack " << stack << std::endl; + if (!stack) { + return in; + } + const auto str = std::get_if<std::string>(&in); + if (!str) { + // strings only! + return in; + } + + auto pos = str->find('$'); + if (pos == std::string::npos) { + // no substitutions! + return in; + } + + if (o) { + stack->push_back(make_pair(o, &in)); + } + string out; + decltype(pos) last_pos = 0; + while (pos != std::string::npos) { + ceph_assert((*str)[pos] == '$'); + if (pos > last_pos) { + out += str->substr(last_pos, pos - last_pos); + } + + // try to parse the variable name into var, either \$\{(.+)\} or + // \$[a-z\_]+ + const char *valid_chars = "abcdefghijklmnopqrstuvwxyz_"; + string var; + size_t endpos = 0; + if ((*str)[pos+1] == '{') { + // ...${foo_bar}... + endpos = str->find_first_not_of(valid_chars, pos + 2); + if (endpos != std::string::npos && + (*str)[endpos] == '}') { + var = str->substr(pos + 2, endpos - pos - 2); + endpos++; + } + } else { + // ...$foo... + endpos = str->find_first_not_of(valid_chars, pos + 1); + if (endpos != std::string::npos) + var = str->substr(pos + 1, endpos - pos - 1); + else + var = str->substr(pos + 1); + } + last_pos = endpos; + + if (!var.size()) { + out += '$'; + } else { + //cout << " found var " << var << std::endl; + // special metavariable? + if (var == "type") { + out += values.name.get_type_name(); + } else if (var == "cluster") { + out += values.cluster; + } else if (var == "name") { + out += values.name.to_cstr(); + } else if (var == "host") { + if (values.host == "") { + out += ceph_get_short_hostname(); + } else { + out += values.host; + } + } else if (var == "num") { + out += values.name.get_id().c_str(); + } else if (var == "id") { + out += values.name.get_id(); + } else if (var == "pid") { + char *_pid = getenv("PID"); + if (_pid) { + out += _pid; + } else { + out += stringify(getpid()); + } + if (o) { + may_reexpand_meta.push_back(o->name); + } + } else if (var == "cctid") { + out += stringify((unsigned long long)this); + } else if (var == "home") { + const char *home = getenv("HOME"); + out = home ? std::string(home) : std::string(); + } else if (var == "programdata") { + const char *home = getenv("ProgramData"); + out = home ? std::string(home) : std::string(); + }else { + if (var == "data_dir") { + var = data_dir_option; + } + const Option *o = find_option(var); + if (!o) { + out += str->substr(pos, endpos - pos); + } else { + auto match = std::find_if( + stack->begin(), stack->end(), + [o](pair<const Option *,const Option::value_t*>& item) { + return item.first == o; + }); + if (match != stack->end()) { + // substitution loop; break the cycle + if (err) { + *err << "variable expansion loop at " << var << "=" + << Option::to_str(*match->second) << "\n" + << "expansion stack:\n"; + for (auto i = stack->rbegin(); i != stack->rend(); ++i) { + *err << i->first->name << "=" + << Option::to_str(*i->second) << "\n"; + } + } + return Option::value_t(std::string("$") + o->name); + } else { + // recursively evaluate! + string n; + conf_stringify(_get_val(values, *o, stack, err), &n); + out += n; + } + } + } + } + pos = str->find('$', last_pos); + } + if (last_pos != std::string::npos) { + out += str->substr(last_pos); + } + if (o) { + stack->pop_back(); + } + + return Option::value_t(out); +} + +int md_config_t::_get_val_cstr( + const ConfigValues& values, + const std::string& key, char **buf, int len) const +{ + if (key.empty()) + return -EINVAL; + + string val; + if (conf_stringify(_get_val(values, key), &val) == 0) { + int l = val.length() + 1; + if (len == -1) { + *buf = (char*)malloc(l); + if (!*buf) + return -ENOMEM; + strncpy(*buf, val.c_str(), l); + return 0; + } + snprintf(*buf, len, "%s", val.c_str()); + return (l > len) ? -ENAMETOOLONG : 0; + } + + // couldn't find a configuration option with key 'k' + return -ENOENT; +} + +void md_config_t::get_all_keys(std::vector<std::string> *keys) const { + const std::string negative_flag_prefix("no_"); + + keys->clear(); + keys->reserve(schema.size()); + for (const auto &i: schema) { + const Option &opt = i.second; + keys->push_back(opt.name); + if (opt.type == Option::TYPE_BOOL) { + keys->push_back(negative_flag_prefix + opt.name); + } + } +} + +/* The order of the sections here is important. The first section in the + * vector is the "highest priority" section; if we find it there, we'll stop + * looking. The lowest priority section is the one we look in only if all + * others had nothing. This should always be the global section. + */ +std::vector <std::string> +md_config_t::get_my_sections(const ConfigValues& values) const +{ + return {values.name.to_str(), + values.name.get_type_name().data(), + "global"}; +} + +// Return a list of all sections +int md_config_t::get_all_sections(std::vector <std::string> §ions) const +{ + for (auto [section_name, section] : cf) { + sections.push_back(section_name); + std::ignore = section; + } + return 0; +} + +int md_config_t::get_val_from_conf_file( + const ConfigValues& values, + const std::vector <std::string> §ions, + const std::string_view key, + std::string &out, + bool emeta) const +{ + int r = _get_val_from_conf_file(sections, key, out); + if (r < 0) { + return r; + } + if (emeta) { + expand_stack_t stack; + auto v = _expand_meta(values, Option::value_t(out), nullptr, &stack, nullptr); + conf_stringify(v, &out); + } + return 0; +} + +int md_config_t::_get_val_from_conf_file( + const std::vector <std::string> §ions, + const std::string_view key, + std::string &out) const +{ + for (auto &s : sections) { + int ret = cf.read(s, key, out); + if (ret == 0) { + return 0; + } else if (ret != -ENOENT) { + return ret; + } + } + return -ENOENT; +} + +int md_config_t::_set_val( + ConfigValues& values, + const ConfigTracker& observers, + const std::string &raw_val, + const Option &opt, + int level, + std::string *error_message) +{ + Option::value_t new_value; + ceph_assert(error_message); + int r = opt.parse_value(raw_val, &new_value, error_message); + if (r < 0) { + return r; + } + + // unsafe runtime change? + if (!opt.can_update_at_runtime() && + safe_to_start_threads && + !observers.is_tracking(opt.name)) { + // accept value if it is not actually a change + if (new_value != _get_val_nometa(values, opt)) { + *error_message = string("Configuration option '") + opt.name + + "' may not be modified at runtime"; + return -EPERM; + } + } + + // Apply the value to its entry in the `values` map + auto result = values.set_value(opt.name, std::move(new_value), level); + switch (result) { + case ConfigValues::SET_NO_CHANGE: + break; + case ConfigValues::SET_NO_EFFECT: + values_bl.clear(); + break; + case ConfigValues::SET_HAVE_EFFECT: + values_bl.clear(); + _refresh(values, opt); + break; + } + return result; +} + +void md_config_t::_refresh(ConfigValues& values, const Option& opt) +{ + // Apply the value to its legacy field, if it has one + auto legacy_ptr_iter = legacy_values.find(std::string(opt.name)); + if (legacy_ptr_iter != legacy_values.end()) { + update_legacy_val(values, opt, legacy_ptr_iter->second); + } + // Was this a debug_* option update? + if (opt.subsys >= 0) { + string actual_val; + conf_stringify(_get_val(values, opt), &actual_val); + values.set_logging(opt.subsys, actual_val.c_str()); + } else { + // normal option, advertise the change. + values.changed.insert(opt.name); + } +} + +int md_config_t::_rm_val(ConfigValues& values, + const std::string_view key, + int level) +{ + if (schema.count(key) == 0) { + return -EINVAL; + } + auto ret = values.rm_val(std::string{key}, level); + if (ret < 0) { + return ret; + } + if (ret == ConfigValues::SET_HAVE_EFFECT) { + _refresh(values, *find_option(key)); + } + values_bl.clear(); + return 0; +} + +namespace { +template<typename Size> +struct get_size_visitor +{ + get_size_visitor() {} + + template<typename T> + Size operator()(const T&) const { + return -1; + } + Size operator()(const Option::size_t& sz) const { + return static_cast<Size>(sz.value); + } + Size operator()(const Size& v) const { + return v; + } +}; + +/** + * Handles assigning from a variant-of-types to a variant-of-pointers-to-types + */ +class assign_visitor +{ + ConfigValues *conf; + Option::value_t val; + public: + + assign_visitor(ConfigValues *conf_, Option::value_t val_) + : conf(conf_), val(val_) + {} + + template <typename T> + void operator()(T ConfigValues::* ptr) const + { + T *member = const_cast<T *>(&(conf->*(ptr))); + + *member = std::get<T>(val); + } + void operator()(uint64_t ConfigValues::* ptr) const + { + using T = uint64_t; + auto member = const_cast<T*>(&(conf->*(ptr))); + *member = std::visit(get_size_visitor<T>{}, val); + } + void operator()(int64_t ConfigValues::* ptr) const + { + using T = int64_t; + auto member = const_cast<T*>(&(conf->*(ptr))); + *member = std::visit(get_size_visitor<T>{}, val); + } +}; +} // anonymous namespace + +void md_config_t::update_legacy_vals(ConfigValues& values) +{ + for (const auto &i : legacy_values) { + const auto &name = i.first; + const auto &option = schema.at(name); + auto ptr = i.second; + update_legacy_val(values, option, ptr); + } +} + +void md_config_t::update_legacy_val(ConfigValues& values, + const Option &opt, + md_config_t::member_ptr_t member_ptr) +{ + Option::value_t v = _get_val(values, opt); + std::visit(assign_visitor(&values, v), member_ptr); +} + +static void dump(Formatter *f, int level, Option::value_t in) +{ + if (const auto v = std::get_if<bool>(&in)) { + f->dump_bool(ceph_conf_level_name(level), *v); + } else if (const auto v = std::get_if<int64_t>(&in)) { + f->dump_int(ceph_conf_level_name(level), *v); + } else if (const auto v = std::get_if<uint64_t>(&in)) { + f->dump_unsigned(ceph_conf_level_name(level), *v); + } else if (const auto v = std::get_if<double>(&in)) { + f->dump_float(ceph_conf_level_name(level), *v); + } else { + f->dump_stream(ceph_conf_level_name(level)) << Option::to_str(in); + } +} + +void md_config_t::diff( + const ConfigValues& values, + Formatter *f, + string name) const +{ + values.for_each([this, f, &values] (auto& name, auto& configs) { + if (configs.empty()) { + return; + } + f->open_object_section(std::string{name}.c_str()); + const Option *o = find_option(name); + if (configs.size() && + configs.begin()->first != CONF_DEFAULT) { + // show compiled-in default only if an override default wasn't provided + dump(f, CONF_DEFAULT, _get_val_default(*o)); + } + for (auto& j : configs) { + dump(f, j.first, j.second); + } + dump(f, CONF_FINAL, _get_val(values, *o)); + f->close_section(); + }); +} + +void md_config_t::complain_about_parse_error(CephContext *cct) +{ + ::complain_about_parse_error(cct, parse_error); +} |