]> git.proxmox.com Git - ceph.git/blobdiff - ceph/src/common/config.cc
update sources to ceph Nautilus 14.2.1
[ceph.git] / ceph / src / common / config.cc
index ef348f95d5c3d86d2a2b0f6b5c7211bb455bae13..b6feaba4b1f177080f1251a742b4d5b13cdeb774 100644 (file)
  *
  */
 
+#include <boost/type_traits.hpp>
+
 #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/backport14.h"
-
-#include <boost/type_traits.hpp>
+#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 ldout
 #undef pdout
 #undef derr
-#undef lderr
 #undef generic_dout
-#undef dendl
+
+// set set_mon_vals()
+#define dout_subsys ceph_subsys_monc
 
 using std::map;
 using std::list;
@@ -42,7 +43,7 @@ using std::ostringstream;
 using std::pair;
 using std::string;
 
-const char *CEPH_CONF_FILE_DEFAULT = "$data_dir/config, /etc/ceph/$cluster.conf, ~/.ceph/$cluster.conf, $cluster.conf"
+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"
 #endif
@@ -51,6 +52,20 @@ const char *CEPH_CONF_FILE_DEFAULT = "$data_dir/config, /etc/ceph/$cluster.conf,
 #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)
 {
@@ -73,14 +88,20 @@ int ceph_resolve_file_search(const std::string& filename_list,
   return ret;
 }
 
-
-
-md_config_t::md_config_t(bool is_daemon)
-  : cluster(""),
-  lock("md_config_t", true, false)
+static int conf_stringify(const Option::value_t& v, string *out)
 {
-  init_subsys();
+  if (boost::get<boost::blank>(&v)) {
+    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) {
@@ -88,9 +109,54 @@ md_config_t::md_config_t(bool is_daemon)
       // We may be instantiated pre-logging so send 
       std::cerr << "Duplicate config key in schema: '" << i.name << "'"
                 << std::endl;
-      assert(false);
+      ceph_abort();
     }
-    schema.insert({i.name, i});
+    schema.emplace(std::piecewise_construct,
+                  std::forward_as_tuple(i.name),
+                  std::forward_as_tuple(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
@@ -100,7 +166,7 @@ md_config_t::md_config_t(bool is_daemon)
   // members if present.
   legacy_values = {
 #define OPTION(name, type) \
-    {std::string(STRINGIFY(name)), &md_config_t::name},
+    {std::string(STRINGIFY(name)), &ConfigValues::name},
 #define SAFE_OPTION(name, type) OPTION(name, type)
 #include "common/legacy_config_opts.h"
 #undef OPTION
@@ -109,44 +175,45 @@ md_config_t::md_config_t(bool is_daemon)
 
   validate_schema();
 
-  // Load default values from the schema
+  // Validate default values from the schema
   for (const auto &i : schema) {
     const Option &opt = i.second;
-    bool has_daemon_default = !boost::get<boost::blank>(&opt.daemon_value);
-    Option::value_t default_val;
-    if (is_daemon && has_daemon_default) {
-      default_val = opt.daemon_value;
-    } else {
-      default_val = opt.value;
-    }
-
     if (opt.type == Option::TYPE_STR) {
+      bool has_daemon_default = !boost::get<boost::blank>(&opt.daemon_value);
+      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.
       std::string *def_str = boost::get<std::string>(&default_val);
+      std::string val = *def_str;
       std::string err;
-      if (opt.pre_validate(def_str, &err) != 0) {
+      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.
-        assert(false);
+        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);
       }
     }
-
-    values[i.first] = default_val;
   }
 
   // Copy out values (defaults) into any legacy (C struct member) fields
-  for (const auto &i : legacy_values) {
-    const auto &name = i.first;
-    const auto &option = schema.at(name);
-    auto ptr = i.second;
+  update_legacy_vals(values);
+}
 
-    update_legacy_val(option, ptr);
-  }
+md_config_t::~md_config_t()
+{
 }
 
 /**
@@ -161,7 +228,7 @@ void md_config_t::validate_schema()
       if (schema.count(see_also_key) == 0) {
         std::cerr << "Non-existent see-also key '" << see_also_key
                   << "' on option '" << opt.name << "'" << std::endl;
-        assert(false);
+        ceph_abort();
       }
     }
   }
@@ -170,119 +237,146 @@ void md_config_t::validate_schema()
     if (schema.count(i.first) == 0) {
       std::cerr << "Schema is missing legacy field '" << i.first << "'"
                 << std::endl;
-      assert(false);
+      ceph_abort();
     }
   }
 }
 
-void md_config_t::init_subsys()
-{
-#define SUBSYS(name, log, gather) \
-  subsys.add(ceph_subsys_##name, STRINGIFY(name), log, gather);
-#define DEFAULT_SUBSYS(log, gather) \
-  subsys.add(ceph_subsys_, "none", log, gather);
-#include "common/subsys.h"
-#undef SUBSYS
-#undef DEFAULT_SUBSYS
-}
-
-md_config_t::~md_config_t()
+const Option *md_config_t::find_option(const string& name) const
 {
+  auto p = schema.find(name);
+  if (p != schema.end()) {
+    return &p->second;
+  }
+  return nullptr;
 }
 
-void md_config_t::add_observer(md_config_obs_t* observer_)
+void md_config_t::set_val_default(ConfigValues& values,
+                                 const ConfigTracker& tracker,
+                                 const string& name, const std::string& val)
 {
-  Mutex::Locker l(lock);
-  const char **keys = observer_->get_tracked_conf_keys();
-  for (const char ** k = keys; *k; ++k) {
-    obs_map_t::value_type val(*k, observer_);
-    observers.insert(val);
-  }
-  obs_call_gate.emplace(observer_, ceph::make_unique<CallGate>());
+  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);
 }
 
-void md_config_t::remove_observer(md_config_obs_t* observer_)
+int md_config_t::set_mon_vals(CephContext *cct,
+    ConfigValues& values,
+    const ConfigTracker& tracker,
+    const map<string,string>& kv,
+    config_callback config_cb)
 {
-  Mutex::Locker l(lock);
+  ignored_mon_values.clear();
 
-  call_gate_close(observer_);
-  obs_call_gate.erase(observer_);
+  if (!config_cb) {
+    ldout(cct, 4) << __func__ << " no callback set" << dendl;
+  }
 
-  bool found_obs = false;
-  for (obs_map_t::iterator o = observers.begin(); o != observers.end(); ) {
-    if (o->second == observer_) {
-      observers.erase(o++);
-      found_obs = true;
+  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;
     }
-    else {
-      ++o;
+    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) {
+      lderr(cct) << __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();
     }
   }
-  assert(found_obs);
+  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);
+  });
+  values_bl.clear();
+  update_legacy_vals(values);
+  return 0;
 }
 
-int md_config_t::parse_config_files(const char *conf_files,
+int md_config_t::parse_config_files(ConfigValues& values,
+                                   const ConfigTracker& tracker,
+                                   const char *conf_files_str,
                                    std::ostream *warnings,
                                    int flags)
 {
-  Mutex::Locker l(lock);
 
-  if (internal_safe_to_start_threads)
+  if (safe_to_start_threads)
     return -ENOSYS;
 
-  if (!cluster.size() && !conf_files) {
+  if (!values.cluster.size() && !conf_files_str) {
     /*
      * set the cluster name to 'ceph' when neither cluster name nor
      * configuration file are specified.
      */
-    cluster = "ceph";
+    values.cluster = "ceph";
   }
 
-  if (!conf_files) {
+  if (!conf_files_str) {
     const char *c = getenv("CEPH_CONF");
     if (c) {
-      conf_files = c;
+      conf_files_str = c;
     }
     else {
       if (flags & CINIT_FLAG_NO_DEFAULT_CONFIG_FILE)
        return 0;
-      conf_files = CEPH_CONF_FILE_DEFAULT;
+      conf_files_str = CEPH_CONF_FILE_DEFAULT;
     }
   }
 
-  std::list<std::string> cfl;
-  get_str_list(conf_files, cfl);
-
-  auto p = cfl.begin();
-  while (p != cfl.end()) {
-    // expand $data_dir?
+  std::list<std::string> conf_files;
+  get_str_list(conf_files_str, conf_files);
+  auto p = conf_files.begin();
+  while (p != conf_files.end()) {
     string &s = *p;
-    if (s.find("$data_dir") != string::npos) {
-      if (data_dir_option.length()) {
-       list<const Option *> stack;
-       expand_meta(s, NULL, stack, warnings);
-       p++;
-      } else {
-       cfl.erase(p++);  // ignore this item
-      }
+    if (s.find("$data_dir") != string::npos &&
+       data_dir_option.empty()) {
+      // useless $data_dir item, skip
+      p = conf_files.erase(p);
     } else {
+      early_expand_meta(values, s, warnings);
       ++p;
     }
   }
-  return parse_config_files_impl(cfl, warnings);
-}
-
-int md_config_t::parse_config_files_impl(const std::list<std::string> &conf_files,
-                                        std::ostream *warnings)
-{
-  assert(lock.is_locked());
 
   // open new conf
   list<string>::const_iterator c;
   for (c = conf_files.begin(); c != conf_files.end(); ++c) {
     cf.clear();
     string fn = *c;
-    expand_meta(fn, warnings);
+
     int ret = cf.parse_file(fn.c_str(), &parse_errors, warnings);
     if (ret == 0)
       break;
@@ -293,7 +387,7 @@ int md_config_t::parse_config_files_impl(const std::list<std::string> &conf_file
   if (c == conf_files.end())
     return -ENOENT;
 
-  if (cluster.size() == 0) {
+  if (values.cluster.size() == 0) {
     /*
      * If cluster name is not set yet, use the prefix of the
      * basename of configuration file as cluster name.
@@ -306,22 +400,22 @@ int md_config_t::parse_config_files_impl(const std::list<std::string> &conf_file
          * convention, we do the last try and assign the cluster to
          * 'ceph'.
          */
-        cluster = "ceph";
+        values.cluster = "ceph";
     } else {
-      cluster = c->substr(start, end - start);
+      values.cluster = c->substr(start, end - start);
     }
   }
 
   std::vector <std::string> my_sections;
-  _get_my_sections(my_sections);
+  _get_my_sections(values, my_sections);
   for (const auto &i : schema) {
     const auto &opt = i.second;
     std::string val;
-    int ret = _get_val_from_conf_file(my_sections, opt.name, val, false);
+    int ret = _get_val_from_conf_file(my_sections, opt.name, val);
     if (ret == 0) {
       std::string error_message;
-      int r = set_val_impl(val, opt, &error_message);
-      if (warnings != nullptr && (r != 0 || !error_message.empty())) {
+      int r = _set_val(values, tracker, val, opt, CONF_FILE, &error_message);
+      if (warnings != nullptr && (r < 0 || !error_message.empty())) {
         *warnings << "parse error setting '" << opt.name << "' to '" << val
                   << "'";
         if (!error_message.empty()) {
@@ -332,25 +426,6 @@ int md_config_t::parse_config_files_impl(const std::list<std::string> &conf_file
     }
   }
 
-  // subsystems?
-  for (size_t o = 0; o < subsys.get_num(); o++) {
-    std::string as_option("debug_");
-    as_option += subsys.get_name(o);
-    std::string val;
-    int ret = _get_val_from_conf_file(my_sections, as_option.c_str(), val, false);
-    if (ret == 0) {
-      int log, gather;
-      int r = sscanf(val.c_str(), "%d/%d", &log, &gather);
-      if (r >= 1) {
-       if (r < 2)
-         gather = log;
-       //      cout << "config subsys " << subsys.get_name(o) << " log " << log << " gather " << gather << std::endl;
-       subsys.set_log_level(o, log);
-       subsys.set_gather_level(o, gather);
-      }
-    }  
-  }
-
   // Warn about section names that look like old-style section names
   std::deque < std::string > old_style_section_names;
   for (ConfFile::const_section_iter_t s = cf.sections_begin();
@@ -372,89 +447,107 @@ int md_config_t::parse_config_files_impl(const std::list<std::string> &conf_file
     }
     cerr << ". Please use the new style section names that include a period.";
   }
+
+  update_legacy_vals(values);
+
   return 0;
 }
 
-void md_config_t::parse_env()
+void md_config_t::parse_env(unsigned entity_type,
+                           ConfigValues& values,
+                           const ConfigTracker& tracker,
+                           const char *args_var)
 {
-  Mutex::Locker l(lock);
-  if (internal_safe_to_start_threads)
+  if (safe_to_start_threads)
     return;
+  if (!args_var) {
+    args_var = "CEPH_ARGS";
+  }
   if (getenv("CEPH_KEYRING")) {
-    set_val_or_die("keyring", getenv("CEPH_KEYRING"));
+    _set_val(values, tracker, getenv("CEPH_KEYRING"), *find_option("keyring"),
+            CONF_ENV, nullptr);
+  }
+  if (const char *dir = getenv("CEPH_LIB")) {
+    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);
+    }
+  }
+  const char *pod_req = getenv("POD_MEMORY_REQUEST");
+  if (pod_req) {
+    uint64_t v = atoll(pod_req);
+    if (v) {
+      switch (entity_type) {
+      case CEPH_ENTITY_TYPE_OSD:
+       _set_val(values, tracker, stringify(v),
+                *find_option("osd_memory_target"),
+                CONF_ENV, nullptr);
+       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(std::ostream& out)
+void md_config_t::show_config(const ConfigValues& values,
+                             std::ostream& out) const
 {
-  Mutex::Locker l(lock);
-  _show_config(&out, NULL);
+  _show_config(values, &out, nullptr);
 }
 
-void md_config_t::show_config(Formatter *f)
+void md_config_t::show_config(const ConfigValues& values,
+                             Formatter *f) const
 {
-  Mutex::Locker l(lock);
-  _show_config(NULL, f);
+  _show_config(values, nullptr, f);
 }
 
-void md_config_t::config_options(Formatter *f)
+void md_config_t::config_options(Formatter *f) const
 {
-  Mutex::Locker l(lock);
   f->open_array_section("options");
   for (const auto& i: schema) {
-    const Option &opt = i.second;
-    opt.dump(f);
+    f->dump_object("option", i.second);
   }
   f->close_section();
 }
 
-void md_config_t::_show_config(std::ostream *out, Formatter *f)
+void md_config_t::_show_config(const ConfigValues& values,
+                              std::ostream *out, Formatter *f) const
 {
   if (out) {
-    *out << "name = " << name << std::endl;
-    *out << "cluster = " << cluster << std::endl;
+    *out << "name = " << values.name << std::endl;
+    *out << "cluster = " << values.cluster << std::endl;
   }
   if (f) {
-    f->dump_string("name", stringify(name));
-    f->dump_string("cluster", cluster);
-  }
-  for (size_t o = 0; o < subsys.get_num(); o++) {
-    if (out)
-      *out << "debug_" << subsys.get_name(o)
-          << " = " << subsys.get_log_level(o)
-          << "/" << subsys.get_gather_level(o) << std::endl;
-    if (f) {
-      ostringstream ss;
-      std::string debug_name = "debug_";
-      debug_name += subsys.get_name(o);
-      ss << subsys.get_log_level(o)
-        << "/" << subsys.get_gather_level(o);
-      f->dump_string(debug_name.c_str(), ss.str());
-    }
+    f->dump_string("name", stringify(values.name));
+    f->dump_string("cluster", values.cluster);
   }
   for (const auto& i: schema) {
     const Option &opt = i.second;
-    char *buf;
-    _get_val(opt.name, &buf, -1);
-    if (out)
-      *out << opt.name << " = " << buf << std::endl;
-    if (f)
-      f->dump_string(opt.name.c_str(), buf);
-    free(buf);
+    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(std::vector<const char*>& args)
+int md_config_t::parse_argv(ConfigValues& values,
+                           const ConfigTracker& tracker,
+                           std::vector<const char*>& args, int level)
 {
-  Mutex::Locker l(lock);
-  if (internal_safe_to_start_threads) {
+  if (safe_to_start_threads) {
     return -ENOSYS;
   }
 
-  bool show_config = false;
-  bool show_config_value = false;
-  string show_config_value_arg;
-
   // 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.
@@ -471,119 +564,112 @@ int md_config_t::parse_argv(std::vector<const char*>& args)
       _exit(0);
     }
     else if (ceph_argparse_flag(args, i, "--show_config", (char*)NULL)) {
-      show_config = true;
+      do_show_config = true;
     }
     else if (ceph_argparse_witharg(args, i, &val, "--show_config_value", (char*)NULL)) {
-      show_config_value = true;
-      show_config_value_arg = val;
+      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, "--log-early", (char*)NULL)) {
+      values.log_early = 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("daemonize", "false");
+      set_val_or_die(values, tracker, "daemonize", "false");
     }
     else if (ceph_argparse_flag(args, i, "-d", (char*)NULL)) {
-      set_val_or_die("daemonize", "false");
-      set_val_or_die("log_file", "");
-      set_val_or_die("log_to_stderr", "true");
-      set_val_or_die("err_to_stderr", "true");
-      set_val_or_die("log_to_syslog", "false");
+      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("monmap", val.c_str());
+      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("mon_host", val.c_str());
+      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("public_addr", val.c_str());
+      set_val_or_die(values, tracker, "public_addr", val.c_str());
     }
     else if (ceph_argparse_witharg(args, i, &val, "--keyfile", "-K", (char*)NULL)) {
-      set_val_or_die("keyfile", val.c_str());
+      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("keyring", val.c_str());
+      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("client_mountpoint", val.c_str());
+      set_val_or_die(values, tracker, "client_mountpoint", val.c_str());
     }
     else {
-      int r = parse_option(args, i, NULL);
+      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 (show_config) {
-    expand_all_meta();
-    _show_config(&cout, NULL);
+  if (do_show_config) {
+    _show_config(values, &cout, NULL);
     _exit(0);
   }
 
-  if (show_config_value) {
-    char *buf = 0;
-    int r = _get_val(show_config_value_arg.c_str(), &buf, -1);
+  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 '" <<
-         show_config_value_arg << "': option not found" << std::endl;
+       std::cerr << "failed to get config option '"
+                 << do_show_config_value << "': option not found" << std::endl;
       else
-       std::cerr << "failed to get config option '" <<
-         show_config_value_arg << "': " << cpp_strerror(r) << std::endl;
+       std::cerr << "failed to get config option '"
+                 << do_show_config_value << "': " << cpp_strerror(r)
+                 << std::endl;
       _exit(1);
     }
-    string s = buf;
-    expand_meta(s, &std::cerr);
-    std::cout << s << std::endl;
+    std::cout << val << std::endl;
     _exit(0);
   }
-
-  return 0;
 }
 
-int md_config_t::parse_option(std::vector<const char*>& args,
-                              std::vector<const char*>::iterator& i,
-                              ostream *oss)
+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;
 
-  // subsystems?
-  for (o = 0; o < subsys.get_num(); o++) {
-    std::string as_option("--");
-    as_option += "debug_";
-    as_option += subsys.get_name(o);
-    ostringstream err;
-    if (ceph_argparse_witharg(args, i, &val, err,
-                             as_option.c_str(), (char*)NULL)) {
-      if (err.tellp()) {
-        if (oss) {
-          *oss << err.str();
-        }
-        ret = -EINVAL;
-        break;
-      }
-      int log, gather;
-      int r = sscanf(val.c_str(), "%d/%d", &log, &gather);
-      if (r >= 1) {
-       if (r < 2)
-         gather = log;
-       //        cout << "subsys " << subsys.get_name(o) << " log " << log << " gather " << gather << std::endl;
-       subsys.set_log_level(o, log);
-       subsys.set_gather_level(o, gather);
-       if (oss)
-         *oss << "debug_" << subsys.get_name(o) << "=" << log << "/" << gather << " ";
-      }
-      break;
-    }  
-  }
-  if (o < subsys.get_num()) {
-    return ret;
-  }
-
   std::string option_name;
   std::string error_message;
   o = 0;
@@ -593,14 +679,24 @@ int md_config_t::parse_option(std::vector<const char*>& args,
     std::string as_option("--");
     as_option += opt.name;
     option_name = opt.name;
-    if (opt.type == Option::TYPE_BOOL) {
+    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_impl("false", opt, &error_message);
+         ret = _set_val(values, tracker, "false", opt, level, &error_message);
        else if (res == 1)
-         ret = set_val_impl("true", opt, &error_message);
+         ret = _set_val(values, tracker, "true", opt, level, &error_message);
        else
          ret = res;
        break;
@@ -608,7 +704,7 @@ int md_config_t::parse_option(std::vector<const char*>& args,
        std::string no("--no-");
        no += opt.name;
        if (ceph_argparse_flag(args, i, no.c_str(), (char*)NULL)) {
-         ret = set_val_impl("false", opt, &error_message);
+         ret = _set_val(values, tracker, "false", opt, level, &error_message);
          break;
        }
       }
@@ -619,19 +715,14 @@ int md_config_t::parse_option(std::vector<const char*>& args,
        ret = -EINVAL;
        break;
       }
-      if (oss && ((!opt.is_safe()) &&
-                 (observers.find(opt.name) == observers.end()))) {
-       *oss << "You cannot change " << opt.name << " using injectargs.\n";
-        return -ENOSYS;
-      }
-      ret = set_val_impl(val, opt, &error_message);
+      ret = _set_val(values, tracker,  val, opt, level, &error_message);
       break;
     }
     ++o;
   }
 
-  if (ret != 0 || !error_message.empty()) {
-    assert(!option_name.empty());
+  if (ret < 0 || !error_message.empty()) {
+    ceph_assert(!option_name.empty());
     if (oss) {
       *oss << "Parse error setting " << option_name << " to '"
            << val << "' using injectargs";
@@ -653,159 +744,84 @@ int md_config_t::parse_option(std::vector<const char*>& args,
     // ignore
     ++i;
   }
-  return ret;
+  return ret >= 0 ? 0 : ret;
 }
 
-int md_config_t::parse_injectargs(std::vector<const char*>& args,
+int md_config_t::parse_injectargs(ConfigValues& values,
+                                 const ConfigTracker& tracker,
+                                 std::vector<const char*>& args,
                                  std::ostream *oss)
 {
-  assert(lock.is_locked());
   int ret = 0;
   for (std::vector<const char*>::iterator i = args.begin(); i != args.end(); ) {
-    int r = parse_option(args, i, oss);
+    int r = parse_option(values, tracker, args, i, oss, CONF_OVERRIDE);
     if (r < 0)
       ret = r;
   }
   return ret;
 }
 
-void md_config_t::apply_changes(std::ostream *oss)
+void md_config_t::set_safe_to_start_threads()
 {
-  rev_obs_map_t rev_obs;
-  {
-    Mutex::Locker l(lock);
-    /*
-     * apply changes until the cluster name is assigned
-     */
-    if (cluster.size()) {
-      for_each_change(
-        oss, [this, &rev_obs](md_config_obs_t *obs, const std::string &key) {
-          map_observer_changes(obs, key, &rev_obs);
-        });
-    }
-  }
-
-  call_observers(rev_obs);
+  safe_to_start_threads = true;
 }
 
-bool md_config_t::_internal_field(const string& s)
+void md_config_t::_clear_safe_to_start_threads()
 {
-  if (s == "internal_safe_to_start_threads")
-    return true;
-  return false;
+  safe_to_start_threads = false;
 }
 
-void md_config_t::for_each_change(std::ostream *oss, config_gather_cb callback)
-{
-  expand_all_meta();
-
-  // expand_all_meta could have modified anything.  Copy it all out again.
-  for (const auto &i : legacy_values) {
-    const auto &name = i.first;
-    const auto &option = schema.at(name);
-    auto ptr = i.second;
-
-    update_legacy_val(option, ptr);
-  }
-
-  std::set <std::string> empty_set;
-  char buf[128];
-  char *bufptr = (char*)buf;
-  for (changed_set_t::const_iterator c = changed.begin();
-       c != changed.end(); ++c) {
-    const std::string &key(*c);
-    pair < obs_map_t::iterator, obs_map_t::iterator >
-      range(observers.equal_range(key));
-    if ((oss) &&
-       (!_get_val(key.c_str(), &bufptr, sizeof(buf))) &&
-       !_internal_field(key)) {
-      (*oss) << key << " = '" << buf << "' ";
-      if (range.first == range.second) {
-       (*oss) << "(not observed, change may require restart) ";
-      }
-    }
-    for (obs_map_t::iterator r = range.first; r != range.second; ++r) {
-      callback(r->second, key);
-    }
-  }
-
-  changed.clear();
-}
-
-void md_config_t::call_all_observers()
-{
-  rev_obs_map_t rev_obs;
-  {
-    Mutex::Locker l(lock);
-
-    expand_all_meta();
-
-    for (auto r = observers.begin(); r != observers.end(); ++r) {
-      map_observer_changes(r->second, r->first, &rev_obs);
-    }
-  }
-
-  call_observers(rev_obs);
-}
-
-int md_config_t::injectargs(const std::string& s, std::ostream *oss)
+int md_config_t::injectargs(ConfigValues& values,
+                           const ConfigTracker& tracker,
+                           const std::string& s, std::ostream *oss)
 {
   int ret;
-  rev_obs_map_t rev_obs;
-  {
-    Mutex::Locker l(lock);
-
-    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(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;
+  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 = ",";
     }
-
-    for_each_change(
-      oss, [this, &rev_obs](md_config_obs_t *obs, const std::string &key) {
-        map_observer_changes(obs, key, &rev_obs);
-      });
+    *oss << "\n";
+    ret = -EINVAL;
   }
-
-  call_observers(rev_obs);
+  update_legacy_vals(values);
   return ret;
 }
 
-void md_config_t::set_val_or_die(const std::string &key,
-                                 const std::string &val,
-                                 bool meta)
+void md_config_t::set_val_or_die(ConfigValues& values,
+                                const ConfigTracker& tracker,
+                                const std::string &key,
+                                const std::string &val)
 {
   std::stringstream err;
-  int ret = set_val(key, val, meta, &err);
+  int ret = set_val(values, tracker, key, val, &err);
   if (ret != 0) {
     std::cerr << "set_val_or_die(" << key << "): " << err.str();
   }
-  assert(ret == 0);
+  ceph_assert(ret == 0);
 }
 
-int md_config_t::set_val(const std::string &key, const char *val,
-    bool meta, std::stringstream *err_ss)
+int md_config_t::set_val(ConfigValues& values,
+                        const ConfigTracker& tracker,
+                        const std::string &key, const char *val,
+                        std::stringstream *err_ss)
 {
-  Mutex::Locker l(lock);
   if (key.empty()) {
     if (err_ss) *err_ss << "No key specified";
     return -EINVAL;
@@ -815,53 +831,17 @@ int md_config_t::set_val(const std::string &key, const char *val,
   }
 
   std::string v(val);
-  if (meta)
-    expand_meta(v, &std::cerr);
 
   string k(ConfFile::normalize_key_name(key));
 
-  // subsystems?
-  if (strncmp(k.c_str(), "debug_", 6) == 0) {
-    for (size_t o = 0; o < subsys.get_num(); o++) {
-      std::string as_option = "debug_" + subsys.get_name(o);
-      if (k == as_option) {
-       int log, gather;
-       int r = sscanf(v.c_str(), "%d/%d", &log, &gather);
-       if (r >= 1) {
-         if (r < 2) {
-           gather = log;
-          }
-         subsys.set_log_level(o, log);
-         subsys.set_gather_level(o, gather);
-          if (err_ss) *err_ss << "Set " << k << " to " << log << "/" << gather;
-         return 0;
-       }
-        if (err_ss) {
-          *err_ss << "Invalid debug level, should be <int> or <int>/<int>";
-        }
-       return -EINVAL;
-      }
-    }  
-  }
-
   const auto &opt_iter = schema.find(k);
   if (opt_iter != schema.end()) {
     const Option &opt = opt_iter->second;
-    if ((!opt.is_safe()) && internal_safe_to_start_threads) {
-      // If threads have been started and the option is not thread safe
-      if (observers.find(opt.name) == observers.end()) {
-        // And there is no observer to safely change it...
-        // You lose.
-        if (err_ss) *err_ss << "Configuration option '" << key << "' may "
-                    "not be modified at runtime";
-        return -ENOSYS;
-      }
-    }
-
     std::string error_message;
-    int r = set_val_impl(v, opt, &error_message);
-    if (r == 0) {
+    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;
     }
@@ -872,23 +852,118 @@ int md_config_t::set_val(const std::string &key, const char *val,
   return -ENOENT;
 }
 
+int md_config_t::rm_val(ConfigValues& values, const std::string& 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;
+  }
+}
 
-int md_config_t::get_val(const std::string &key, char **buf, int len) const
+int md_config_t::get_val(const ConfigValues& values,
+                        const std::string &key, char **buf, int len) const
 {
-  Mutex::Locker l(lock);
-  return _get_val(key, buf,len);
+  string k(ConfFile::normalize_key_name(key));
+  return _get_val_cstr(values, k, buf, len);
 }
 
-Option::value_t md_config_t::get_val_generic(const std::string &key) const
+int md_config_t::get_val(
+  const ConfigValues& values,
+  const std::string &key,
+  std::string *val) const
 {
-  Mutex::Locker l(lock);
-  return _get_val(key);
+  return conf_stringify(get_val_generic(values, key), val);
 }
 
-Option::value_t md_config_t::_get_val(const std::string &key) const
+Option::value_t md_config_t::get_val_generic(
+  const ConfigValues& values,
+  const std::string &key) const
 {
-  assert(lock.is_locked());
+  string k(ConfFile::normalize_key_name(key));
+  return _get_val(values, k);
+}
 
+Option::value_t md_config_t::_get_val(
+  const ConfigValues& values,
+  const std::string &key,
+  expand_stack_t *stack,
+  std::ostream *err) const
+{
   if (key.empty()) {
     return Option::value_t(boost::blank());
   }
@@ -896,45 +971,221 @@ Option::value_t md_config_t::_get_val(const std::string &key) const
   // In key names, leading and trailing whitespace are not significant.
   string k(ConfFile::normalize_key_name(key));
 
-  const auto &opt_iter = schema.find(k);
-  if (opt_iter != schema.end()) {
-    // Using .at() is safe because all keys in the schema always have
-    // entries in ::values
-    return values.at(k);
-  } else {
+  const Option *o = find_option(key);
+  if (!o) {
+    // not a valid config option
     return Option::value_t(boost::blank());
   }
+
+  return _get_val(values, *o, stack, err);
 }
 
-int md_config_t::_get_val(const std::string &key, std::string *value) const {
-  assert(lock.is_locked());
+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);
+}
 
-  std::string normalized_key(ConfFile::normalize_key_name(key));
-  Option::value_t config_value = _get_val(normalized_key.c_str());
-  if (!boost::get<boost::blank>(&config_value)) {
-    ostringstream oss;
-    if (bool *flag = boost::get<bool>(&config_value)) {
-      oss << (*flag ? "true" : "false");
-    } else if (double *dp = boost::get<double>(&config_value)) {
-      oss << std::fixed << *dp;
+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 = !boost::get<boost::blank>(&o.daemon_value);
+  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)
+{
+  for (auto& [name, value] : may_reexpand_meta) {
+    set_val(values, tracker, name, value);
+  }
+  
+  if (!may_reexpand_meta.empty()) {
+    // meta expands could have modified anything.  Copy it all out again.
+    update_legacy_vals(values);
+    return true;
+  } else {
+    return false;
+  }
+}
+
+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 std::string *str = boost::get<const 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 {
-      oss << config_value;
+      // ...$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);
     }
-    *value = oss.str();
-    return 0;
+    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") {
+       out += stringify(getpid());
+        if (o) {
+          may_reexpand_meta[o->name] = *str;
+        }
+      } 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 == "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);
   }
-  return -ENOENT;
+  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(const std::string &key, char **buf, int len) const
+int md_config_t::_get_val_cstr(
+  const ConfigValues& values,
+  const std::string &key, char **buf, int len) const
 {
-  assert(lock.is_locked());
-
   if (key.empty())
     return -EINVAL;
 
-  string val ;
-  if (_get_val(key, &val) == 0) {
+  string val;
+  if (conf_stringify(_get_val(values, key), &val) == 0) {
     int l = val.length() + 1;
     if (len == -1) {
       *buf = (char*)malloc(l);
@@ -948,18 +1199,6 @@ int md_config_t::_get_val(const std::string &key, char **buf, int len) const
   }
 
   string k(ConfFile::normalize_key_name(key));
-  // subsys?
-  for (size_t o = 0; o < subsys.get_num(); o++) {
-    std::string as_option = "debug_" + subsys.get_name(o);
-    if (k == as_option) {
-      if (len == -1) {
-       *buf = (char*)malloc(20);
-       len = 20;
-      }
-      int l = snprintf(*buf, len, "%d/%d", subsys.get_log_level(o), subsys.get_gather_level(o));
-      return (l == len) ? -ENAMETOOLONG : 0;
-    }
-  }
 
   // couldn't find a configuration option with key 'k'
   return -ENOENT;
@@ -977,9 +1216,6 @@ void md_config_t::get_all_keys(std::vector<std::string> *keys) const {
       keys->push_back(negative_flag_prefix + opt.name);
     }
   }
-  for (size_t i = 0; i < subsys.get_num(); ++i) {
-    keys->push_back("debug_" + subsys.get_name(i));
-  }
 }
 
 /* The order of the sections here is important.  The first section in the
@@ -987,18 +1223,18 @@ void md_config_t::get_all_keys(std::vector<std::string> *keys) const {
  * looking. The lowest priority section is the one we look in only if all
  * others had nothing.  This should always be the global section.
  */
-void md_config_t::get_my_sections(std::vector <std::string> &sections) const
+void md_config_t::get_my_sections(const ConfigValues& values,
+                                 std::vector <std::string> &sections) const
 {
-  Mutex::Locker l(lock);
-  _get_my_sections(sections);
+  _get_my_sections(values, sections);
 }
 
-void md_config_t::_get_my_sections(std::vector <std::string> &sections) const
+void md_config_t::_get_my_sections(const ConfigValues& values,
+                                  std::vector <std::string> &sections) const
 {
-  assert(lock.is_locked());
-  sections.push_back(name.to_str());
+  sections.push_back(values.name.to_str());
 
-  sections.push_back(name.get_type_name());
+  sections.push_back(values.name.get_type_name());
 
   sections.push_back("global");
 }
@@ -1006,7 +1242,6 @@ void md_config_t::_get_my_sections(std::vector <std::string> &sections) const
 // Return a list of all sections
 int md_config_t::get_all_sections(std::vector <std::string> &sections) const
 {
-  Mutex::Locker l(lock);
   for (ConfFile::const_section_iter_t s = cf.sections_begin();
        s != cf.sections_end(); ++s) {
     sections.push_back(s->first);
@@ -1014,406 +1249,231 @@ int md_config_t::get_all_sections(std::vector <std::string> &sections) const
   return 0;
 }
 
-int md_config_t::get_val_from_conf_file(const std::vector <std::string> &sections,
-                   const std::string &key, std::string &out, bool emeta) const
+int md_config_t::get_val_from_conf_file(
+  const ConfigValues& values,
+  const std::vector <std::string> &sections,
+  const std::string &key,
+  std::string &out,
+  bool emeta) const
 {
-  Mutex::Locker l(lock);
-  return _get_val_from_conf_file(sections, key, out, emeta);
+  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> &sections,
-                                        const std::string &key, std::string &out, bool emeta) const
+int md_config_t::_get_val_from_conf_file(
+  const std::vector <std::string> &sections,
+  const std::string &key,
+  std::string &out) const
 {
-  assert(lock.is_locked());
   std::vector <std::string>::const_iterator s = sections.begin();
   std::vector <std::string>::const_iterator s_end = sections.end();
   for (; s != s_end; ++s) {
     int ret = cf.read(s->c_str(), key, out);
     if (ret == 0) {
-      if (emeta)
-       expand_meta(out, &std::cerr);
       return 0;
-    }
-    else if (ret != -ENOENT)
+    } else if (ret != -ENOENT) {
       return ret;
+    }
   }
   return -ENOENT;
 }
 
-int md_config_t::set_val_impl(const std::string &raw_val, const Option &opt,
-                              std::string *error_message)
+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)
 {
-  assert(lock.is_locked());
-
-  std::string val = raw_val;
-
-  int r = opt.pre_validate(&val, error_message);
-  if (r != 0) {
+  Option::value_t new_value;
+  int r = opt.parse_value(raw_val, &new_value, error_message);
+  if (r < 0) {
     return r;
   }
 
-  Option::value_t new_value;
-  if (opt.type == Option::TYPE_INT) {
-    int64_t f = strict_si_cast<int64_t>(val.c_str(), error_message);
-    if (!error_message->empty()) {
-      return -EINVAL;
-    }
-    new_value = f;
-  } else if (opt.type == Option::TYPE_UINT) {
-    uint64_t f = strict_si_cast<uint64_t>(val.c_str(), error_message);
-    if (!error_message->empty()) {
-      return -EINVAL;
-    }
-    new_value = f;
-  } else if (opt.type == Option::TYPE_STR) {
-    new_value = val;
-  } else if (opt.type == Option::TYPE_FLOAT) {
-    double f = strict_strtod(val.c_str(), error_message);
-    if (!error_message->empty()) {
-      return -EINVAL;
-    } else {
-      new_value = f;
-    }
-  } else if (opt.type == Option::TYPE_BOOL) {
-    if (strcasecmp(val.c_str(), "false") == 0) {
-      new_value = false;
-    } else if (strcasecmp(val.c_str(), "true") == 0) {
-      new_value = true;
-    } else {
-      int b = strict_strtol(val.c_str(), 10, error_message);
-      if (!error_message->empty()) {
-       return -EINVAL;
-      }
-      new_value = !!b;
-    }
-  } else if (opt.type == Option::TYPE_ADDR) {
-    entity_addr_t addr;
-    if (!addr.parse(val.c_str())){
-      return -EINVAL;
+  // 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 -ENOSYS;
     }
-    new_value = addr;
-  } else if (opt.type == Option::TYPE_UUID) {
-    uuid_d uuid;
-    if (!uuid.parse(val.c_str())) {
-      return -EINVAL;
-    }
-    new_value = uuid;
-  } else {
-    ceph_abort();
-  }
-
-  r = opt.validate(new_value, error_message);
-  if (r != 0) {
-    return r;
   }
 
-
   // Apply the value to its entry in the `values` map
-  values[opt.name] = new_value;
+  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(opt, legacy_ptr_iter->second);
+    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);
   }
+}
 
-  changed.insert(opt.name);
+int md_config_t::_rm_val(ConfigValues& values,
+                        const std::string& key,
+                        int level)
+{
+  if (schema.count(key) == 0) {
+    return -EINVAL;
+  }
+  auto ret = values.rm_val(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 : public boost::static_visitor<Size>
+{
+  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
  */
+template<class Config>
 class assign_visitor : public boost::static_visitor<>
 {
-  md_config_t *conf;
+  Config *conf;
   Option::value_t val;
   public:
 
-  assign_visitor(md_config_t *conf_, Option::value_t val_)
+  assign_visitor(Config *conf_, Option::value_t val_)
     : conf(conf_), val(val_)
   {}
 
   template <typename T>
-  void operator()( T md_config_t::* ptr) const
+  void operator()(T Config::* ptr) const
   {
-    T *member = const_cast<T *>(&(conf->*(boost::get<const T md_config_t::*>(ptr))));
+    T *member = const_cast<T *>(&(conf->*(boost::get<const T Config::*>(ptr))));
 
     *member = boost::get<T>(val);
   }
-};
-
-void md_config_t::update_legacy_val(const Option &opt,
-                                    md_config_t::member_ptr_t member_ptr)
-{
-  if (boost::get<boost::blank>(&values.at(opt.name))) {
-    // This shouldn't happen, but if it does then just don't even
-    // try to assign to the legacy field.
-    return;
+  void operator()(uint64_t Config::* ptr) const
+  {
+    using T = uint64_t;
+    auto member = const_cast<T*>(&(conf->*(boost::get<const T Config::*>(ptr))));
+    *member = boost::apply_visitor(get_size_visitor<T>{}, val);
+  }
+  void operator()(int64_t Config::* ptr) const
+  {
+    using T = int64_t;
+    auto member = const_cast<T*>(&(conf->*(boost::get<const T Config::*>(ptr))));
+    *member = boost::apply_visitor(get_size_visitor<T>{}, val);
   }
-
-  boost::apply_visitor(assign_visitor(this, values.at(opt.name)), member_ptr);
-}
-
-
-static const char *CONF_METAVARIABLES[] = {
-  "data_dir", // put this first: it may contain some of the others
-  "cluster", "type", "name", "host", "num", "id", "pid", "cctid"
 };
-static const int NUM_CONF_METAVARIABLES =
-      (sizeof(CONF_METAVARIABLES) / sizeof(CONF_METAVARIABLES[0]));
+} // anonymous namespace
 
-void md_config_t::expand_all_meta()
+void md_config_t::update_legacy_vals(ConfigValues& values)
 {
-  // Expand all metavariables
-  ostringstream oss;
-  for (const auto &i : schema) {
-    const Option &opt = i.second;
-
-    if (opt.type == Option::TYPE_STR) {
-      list<const Option*> stack;
-      std::string *str = boost::get<std::string>(&(values.at(opt.name)));
-      assert(str != nullptr);  // Non-string values should never get in
-      expand_meta(*str, &opt, stack, &oss);
-    }
+  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);
   }
-  cerr << oss.str();
 }
 
-bool md_config_t::expand_meta(std::string &val,
-                             std::ostream *oss) const
+void md_config_t::update_legacy_val(ConfigValues& values,
+                                   const Option &opt,
+                                    md_config_t::member_ptr_t member_ptr)
 {
-  list<const Option*> stack;
-  return expand_meta(val, NULL, stack, oss);
+  Option::value_t v = _get_val(values, opt);
+  boost::apply_visitor(assign_visitor(&values, v), member_ptr);
 }
 
-bool md_config_t::expand_meta(std::string &origval,
-                             const Option *opt,
-                             std::list<const Option *> stack,
-                             std::ostream *oss) const
+static void dump(Formatter *f, int level, Option::value_t in)
 {
-  assert(lock.is_locked());
-
-  // no $ means no variable expansion is necessary
-  if (origval.find("$") == string::npos)
-    return false;
-
-  // ignore an expansion loop and create a human readable
-  // message about it
-  if (opt) {
-    for (const auto stack_ptr : stack) {
-      if (opt->name == stack_ptr->name) {
-       *oss << "variable expansion loop at "
-            << opt->name << "=" << origval << std::endl;
-       *oss << "expansion stack: " << std::endl;
-       for (const auto j : stack) {
-          std::string val;
-          _get_val(j->name, &val);
-         *oss << j->name << "=" << val << std::endl;
-       }
-       return false;
-      }
-    }
-
-    stack.push_front(opt);
-  }
-
-  bool found_meta = false;
-  string out;
-  string val = origval;
-  for (string::size_type s = 0; s < val.size(); ) {
-    if (val[s] != '$') {
-      out += val[s++];
-      continue;
-    }
-
-    // try to parse the variable name into var, either \$\{(.+)\} or
-    // \$[a-z\_]+
-    const char *valid_chars = "abcdefghijklmnopqrstuvwxyz_";
-    string var;
-    size_t endpos = 0;
-    if (val[s+1] == '{') {
-      // ...${foo_bar}...
-      endpos = val.find_first_not_of(valid_chars, s+2);
-      if (endpos != std::string::npos &&
-         val[endpos] == '}') {
-       var = val.substr(s+2, endpos-s-2);
-       endpos++;
-      }
-    } else {
-      // ...$foo...
-      endpos = val.find_first_not_of(valid_chars, s+1);
-      if (endpos != std::string::npos)
-       var = val.substr(s+1, endpos-s-1);
-      else
-       var = val.substr(s+1);
-    }
-
-    bool expanded = false;
-    if (var.length()) {
-      // special metavariable?
-      for (int i = 0; i < NUM_CONF_METAVARIABLES; ++i) {
-       if (var != CONF_METAVARIABLES[i])
-         continue;
-       //cout << "  meta match of " << var << " " << CONF_METAVARIABLES[i] << std::endl;
-       if (var == "type")
-         out += name.get_type_name();
-       else if (var == "cluster")
-         out += cluster;
-       else if (var == "name")
-          out += name.to_cstr();
-       else if (var == "host")
-        {
-          if (host == "")
-            out += ceph_get_short_hostname();
-          else
-           out += host;
-        }
-       else if (var == "num")
-         out += name.get_id().c_str();
-       else if (var == "id")
-         out += name.get_id().c_str();
-       else if (var == "pid")
-         out += stringify(getpid());
-       else if (var == "cctid")
-         out += stringify((unsigned long long)this);
-       else if (var == "data_dir") {
-         if (data_dir_option.length()) {
-           char *vv = NULL;
-           _get_val(data_dir_option.c_str(), &vv, -1);
-           string tmp = vv;
-           free(vv);
-           expand_meta(tmp, NULL, stack, oss);
-           out += tmp;
-         } else {
-           // this isn't really right, but it'll result in a mangled
-           // non-existent path that will fail any search list
-           out += "$data_dir";
-         }
-       } else
-         ceph_abort(); // unreachable
-       expanded = true;
-      }
-
-      if (!expanded) {
-       // config option?
-        const auto other_opt_iter = schema.find(var);
-        if (other_opt_iter != schema.end()) {
-          const Option &other_opt = other_opt_iter->second;
-          if (other_opt.type == Option::TYPE_STR) {
-            // The referenced option is a string, it may need substitution
-            // before inserting.
-            Option::value_t *other_val_ptr = const_cast<Option::value_t*>(&(values.at(other_opt.name)));
-            std::string *other_opt_val = boost::get<std::string>(other_val_ptr);
-            expand_meta(*other_opt_val, &other_opt, stack, oss);
-            out += *other_opt_val;
-          } else {
-            // The referenced option is not a string: retrieve and insert
-            // its stringized form.
-            char *vv = NULL;
-            _get_val(other_opt.name, &vv, -1);
-            out += vv;
-            free(vv);
-          }
-          expanded = true;
-       }
-      }
-    }
-
-    if (expanded) {
-      found_meta = true;
-      s = endpos;
-    } else {
-      out += val[s++];
-    }
+  if (const bool *v = boost::get<const bool>(&in)) {
+    f->dump_bool(ceph_conf_level_name(level), *v);
+  } else if (const int64_t *v = boost::get<const int64_t>(&in)) {
+    f->dump_int(ceph_conf_level_name(level), *v);
+  } else if (const uint64_t *v = boost::get<const uint64_t>(&in)) {
+    f->dump_unsigned(ceph_conf_level_name(level), *v);
+  } else if (const double *v = boost::get<const double>(&in)) {
+    f->dump_float(ceph_conf_level_name(level), *v);
+  } else {
+    f->dump_stream(ceph_conf_level_name(level)) << Option::to_str(in);
   }
-  // override the original value with the expanded value
-  origval = out;
-  return found_meta;
 }
 
 void md_config_t::diff(
-  const md_config_t *other,
-  map<string, pair<string, string> > *diff,
-  set<string> *unknown) 
-{
-  diff_helper(other, diff, unknown);
-}
-void md_config_t::diff(
-  const md_config_t *other,
-  map<string, pair<string, string> > *diff,
-  set<string> *unknown, const string& setting) 
-{
-  diff_helper(other, diff, unknown, setting);
-}
-
-void md_config_t::diff_helper(
-    const md_config_t *other,
-    map<string,pair<string,string> > *diff,
-    set<string> *unknown, const string& setting)
+  const ConfigValues& values,
+  Formatter *f,
+  string name) const
 {
-  Mutex::Locker l(lock);
-
-  char local_buf[4096];
-  char other_buf[4096];
-  for (const auto &i : schema) {
-    const Option &opt = i.second;
-    if (!setting.empty()) {
-      if (setting != opt.name) {
-        continue;
-      }
-    }
-    memset(local_buf, 0, sizeof(local_buf));
-    memset(other_buf, 0, sizeof(other_buf));
-
-    char *other_val = other_buf;
-    int err = other->get_val(opt.name, &other_val, sizeof(other_buf));
-    if (err < 0) {
-      if (err == -ENOENT) {
-        unknown->insert(opt.name);
-      }
-      continue;
+  values.for_each([this, f, &values] (auto& name, auto& configs) {
+    if (configs.size() == 1 &&
+       configs.begin()->first == CONF_DEFAULT) {
+      // we only have a default value; exclude from diff
+      return;
     }
-
-    char *local_val = local_buf;
-    err = _get_val(opt.name, &local_val, sizeof(local_buf));
-    if (err != 0)
-      continue;
-
-    if (strcmp(local_val, other_val))
-      diff->insert(make_pair(opt.name, make_pair(local_val, other_val)));
-    else if (!setting.empty()) {
-        diff->insert(make_pair(opt.name, make_pair(local_val, other_val)));
-        break;
+    f->open_object_section(name.c_str());
+    const Option *o = find_option(name);
+    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_errors(CephContext *cct)
 {
   ::complain_about_parse_errors(cct, &parse_errors);
 }
-
-void md_config_t::call_observers(rev_obs_map_t &rev_obs) {
-  for (auto p : rev_obs) {
-    p.first->handle_conf_change(this, p.second);
-    // this can be done outside the lock as call_gate_enter()
-    // and remove_observer() are serialized via lock
-    call_gate_leave(p.first);
-  }
-}
-
-void md_config_t::map_observer_changes(md_config_obs_t *obs, const std::string &key,
-                                       rev_obs_map_t *rev_obs) {
-  ceph_assert(lock.is_locked());
-
-  auto p = rev_obs->emplace(obs, std::set<std::string>{});
-
-  p.first->second.emplace(key);
-  if (p.second) {
-    // this needs to be done under lock as once this lock is
-    // dropped (before calling observers) a remove_observer()
-    // can sneak in and cause havoc.
-    call_gate_enter(p.first->first);
-  }
-}