]> git.proxmox.com Git - ceph.git/blobdiff - ceph/src/common/cmdparse.cc
update sources to ceph Nautilus 14.2.1
[ceph.git] / ceph / src / common / cmdparse.cc
index a93e92c3e88dcc22a309ed41cf1d10477536ac39..5945c0baf766da37759e708ed639172d7b40c2df 100644 (file)
  *
  */
 
-#include "json_spirit/json_spirit.h"
+#include "common/cmdparse.h"
+#include "common/Formatter.h"
 #include "common/debug.h"
-
-using namespace std;
+#include "common/strtol.h"
+#include "json_spirit/json_spirit.h"
 
 /**
  * Given a cmddesc like "foo baz name=bar,type=CephString",
@@ -42,6 +43,77 @@ std::string cmddesc_get_prefix(const std::string &cmddesc)
   return result.str();
 }
 
+using arg_desc_t = std::map<std::string_view, std::string_view>;
+
+// Snarf up all the key=val,key=val pairs, put 'em in a dict.
+template<class String>
+arg_desc_t cmddesc_get_args(const String& cmddesc)
+{
+  arg_desc_t arg_desc;
+  for_each_substr(cmddesc, ",", [&](auto kv) {
+      // key=value; key by itself implies value is bool true
+      // name="name" means arg dict will be titled 'name'
+      auto equal = kv.find('=');
+      if (equal == kv.npos) {
+       // it should be the command
+       return;
+      }
+      auto key = kv.substr(0, equal);
+      auto val = kv.substr(equal + 1);
+      arg_desc[key] = val;
+    });
+  return arg_desc;
+}
+
+std::string cmddesc_get_prenautilus_compat(const std::string &cmddesc)
+{
+  std::vector<std::string> out;
+  stringstream ss(cmddesc);
+  std::string word;
+  bool changed = false;
+  while (std::getline(ss, word, ' ')) {
+    // if no , or =, must be a plain word to put out
+    if (word.find_first_of(",=") == string::npos) {
+      out.push_back(word);
+      continue;
+    }
+    auto desckv = cmddesc_get_args(word);
+    auto j = desckv.find("type");
+    if (j != desckv.end() && j->second == "CephBool") {
+      // Instruct legacy clients or mons to send --foo-bar string in place
+      // of a 'true'/'false' value
+      std::ostringstream oss;
+      oss << std::string("--") << desckv["name"];
+      std::string val = oss.str();
+      std::replace(val.begin(), val.end(), '_', '-');
+      desckv["type"] = "CephChoices";
+      desckv["strings"] = val;
+      std::ostringstream fss;
+      for (auto k = desckv.begin(); k != desckv.end(); ++k) {
+       if (k != desckv.begin()) {
+         fss << ",";
+       }
+       fss << k->first << "=" << k->second;
+      }
+      out.push_back(fss.str());
+      changed = true;
+    } else {
+      out.push_back(word);
+    }
+  }
+  if (!changed) {
+    return cmddesc;
+  }
+  std::string o;
+  for (auto i = out.begin(); i != out.end(); ++i) {
+    if (i != out.begin()) {
+      o += " ";
+    }
+    o += *i;
+  }
+  return o;
+}
+
 /**
  * Read a command description list out of cmd, and dump it to f.
  * A signature description is a set of space-separated words;
@@ -49,7 +121,7 @@ std::string cmddesc_get_prefix(const std::string &cmddesc)
  */
 
 void
-dump_cmd_to_json(Formatter *f, const string& cmd)
+dump_cmd_to_json(Formatter *f, uint64_t features, const string& cmd)
 {
   // put whole command signature in an already-opened container
   // elements are: "name", meaning "the typeless name that means a literal"
@@ -64,33 +136,31 @@ dump_cmd_to_json(Formatter *f, const string& cmd)
       f->dump_string("arg", word);
       continue;
     }
-    // Snarf up all the key=val,key=val pairs, put 'em in a dict.
-    // no '=val' implies '=True'.
-    std::stringstream argdesc(word);
-    std::string keyval;
-    std::map<std::string, std::string>desckv;
     // accumulate descriptor keywords in desckv
-
-    while (std::getline(argdesc, keyval, ',')) {
-      // key=value; key by itself implies value is bool true
-      // name="name" means arg dict will be titled 'name'
-      size_t pos = keyval.find('=');
-      std::string key, val;
-      if (pos != std::string::npos) {
-       key = keyval.substr(0, pos);
-       val = keyval.substr(pos+1);
-      } else {
-        key = keyval;
-        val = true;
+    auto desckv = cmddesc_get_args(word);
+    // name the individual desc object based on the name key
+    f->open_object_section(string(desckv["name"]).c_str());
+
+    // Compatibility for pre-nautilus clients that don't know about CephBool
+    std::string val;
+    if (!HAVE_FEATURE(features, SERVER_NAUTILUS)) {
+      auto i = desckv.find("type");
+      if (i != desckv.end() && i->second == "CephBool") {
+        // Instruct legacy clients to send --foo-bar string in place
+        // of a 'true'/'false' value
+        std::ostringstream oss;
+        oss << std::string("--") << desckv["name"];
+        val = oss.str();
+        std::replace(val.begin(), val.end(), '_', '-');
+
+        desckv["type"] = "CephChoices";
+        desckv["strings"] = val;
       }
-      desckv.insert(std::pair<std::string, std::string> (key, val));
     }
-    // name the individual desc object based on the name key
-    f->open_object_section(desckv["name"].c_str());
+
     // dump all the keys including name into the array
-    for (std::map<std::string, std::string>::iterator it = desckv.begin();
-        it != desckv.end(); ++it) {
-      f->dump_string(it->first.c_str(), it->second);
+    for (auto [key, value] : desckv) {
+      f->dump_string(string(key).c_str(), string(value));
     }
     f->close_section(); // attribute object for individual desc
   }
@@ -98,13 +168,14 @@ dump_cmd_to_json(Formatter *f, const string& cmd)
 
 void
 dump_cmd_and_help_to_json(Formatter *jf,
+                         uint64_t features,
                          const string& secname,
                          const string& cmdsig,
                          const string& helptext)
 {
       jf->open_object_section(secname.c_str());
       jf->open_array_section("sig");
-      dump_cmd_to_json(jf, cmdsig);
+      dump_cmd_to_json(jf, features, cmdsig);
       jf->close_section(); // sig array
       jf->dump_string("help", helptext.c_str());
       jf->close_section(); // cmd
@@ -112,29 +183,28 @@ dump_cmd_and_help_to_json(Formatter *jf,
 
 void
 dump_cmddesc_to_json(Formatter *jf,
+                    uint64_t features,
                     const string& secname,
                     const string& cmdsig,
                     const string& helptext,
                     const string& module,
                     const string& perm,
-                    const string& avail,
                     uint64_t flags)
 {
       jf->open_object_section(secname.c_str());
       jf->open_array_section("sig");
-      dump_cmd_to_json(jf, cmdsig);
+      dump_cmd_to_json(jf, features, cmdsig);
       jf->close_section(); // sig array
       jf->dump_string("help", helptext.c_str());
       jf->dump_string("module", module.c_str());
       jf->dump_string("perm", perm.c_str());
-      jf->dump_string("avail", avail.c_str());
       jf->dump_int("flags", flags);
       jf->close_section(); // cmd
 }
 
 void cmdmap_dump(const cmdmap_t &cmdmap, Formatter *f)
 {
-  assert(f != nullptr);
+  ceph_assert(f != nullptr);
 
   class dump_visitor : public boost::static_visitor<void>
   {
@@ -210,7 +280,7 @@ void cmdmap_dump(const cmdmap_t &cmdmap, Formatter *f)
  * false, ss is valid */
 
 bool
-cmdmap_from_json(vector<string> cmd, map<string, cmd_vartype> *mapp, stringstream &ss)
+cmdmap_from_json(vector<string> cmd, cmdmap_t *mapp, stringstream &ss)
 {
   json_spirit::mValue v;
 
@@ -392,3 +462,204 @@ int parse_osd_id(const char *s, std::ostream *pss)
   }
   return id;
 }
+
+namespace {
+template <typename Func>
+bool find_first_in(std::string_view s, const char *delims, Func&& f)
+{
+  auto pos = s.find_first_not_of(delims);
+  while (pos != s.npos) {
+    s.remove_prefix(pos);
+    auto end = s.find_first_of(delims);
+    if (f(s.substr(0, end))) {
+      return true;
+    }
+    pos = s.find_first_not_of(delims, end);
+  }
+  return false;
+}
+
+template<typename T>
+T str_to_num(const std::string& s)
+{
+  if constexpr (is_same_v<T, int>) {
+    return std::stoi(s);
+  } else if constexpr (is_same_v<T, long>) {
+    return std::stol(s);
+  } else if constexpr (is_same_v<T, long long>) {
+    return std::stoll(s);
+  } else if constexpr (is_same_v<T, double>) {
+    return std::stod(s);
+  }
+}
+
+template<typename T>
+bool arg_in_range(T value, const arg_desc_t& desc, std::ostream& os) {
+  auto range = desc.find("range");
+  if (range == desc.end()) {
+    return true;
+  }
+  auto min_max = get_str_list(string(range->second), "|");
+  auto min = str_to_num<T>(min_max.front());
+  auto max = numeric_limits<T>::max();
+  if (min_max.size() > 1) {
+    max = str_to_num<T>(min_max.back());
+  }
+  if (value < min || value > max) {
+    os << "'" << value << "' out of range: " << min_max;
+    return false;
+  }
+  return true;
+}
+
+bool validate_str_arg(std::string_view value,
+                     std::string_view type,
+                     const arg_desc_t& desc,
+                     std::ostream& os)
+{
+  if (type == "CephIPAddr") {
+    entity_addr_t addr;
+    if (addr.parse(string(value).c_str())) {
+      return true;
+    } else {
+      os << "failed to parse addr '" << value << "', should be ip:[port]";
+      return false;
+    }
+  } else if (type == "CephChoices") {
+    auto choices = desc.find("strings");
+    ceph_assert(choices != end(desc));
+    auto strings = choices->second;
+    if (find_first_in(strings, "|", [=](auto choice) {
+         return (value == choice);
+       })) {
+      return true;
+    } else {
+      os << "'" << value << "' not belong to '" << strings << "'";
+      return false;
+    }
+  } else {
+    // CephString or other types like CephPgid
+    return true;
+  }
+}
+
+template<bool is_vector,
+        typename T,
+        typename Value = conditional_t<is_vector,
+                                       vector<T>,
+                                       T>>
+bool validate_arg(CephContext* cct,
+                 const cmdmap_t& cmdmap,
+                 const arg_desc_t& desc,
+                 const std::string_view name,
+                 const std::string_view type,
+                 std::ostream& os)
+{
+  Value v;
+  try {
+    if (!cmd_getval(cct, cmdmap, string(name), v)) {
+      if constexpr (is_vector) {
+         // an empty list is acceptable.
+         return true;
+       } else {
+       if (auto req = desc.find("req");
+           req != end(desc) && req->second == "false") {
+         return true;
+       } else {
+         os << "missing required parameter: '" << name << "'";
+         return false;
+       }
+      }
+    }
+  } catch (const bad_cmd_get& e) {
+    return false;
+  }
+  auto validate = [&](const T& value) {
+    if constexpr (is_same_v<std::string, T>) {
+      return validate_str_arg(value, type, desc, os);
+    } else if constexpr (is_same_v<int64_t, T> ||
+                        is_same_v<double, T>) {
+      return arg_in_range(value, desc, os);
+    }
+  };
+  if constexpr(is_vector) {
+    return find_if_not(begin(v), end(v), validate) == end(v);
+  } else {
+    return validate(v);
+  }
+}
+} // anonymous namespace
+
+bool validate_cmd(CephContext* cct,
+                 const std::string& desc,
+                 const cmdmap_t& cmdmap,
+                 std::ostream& os)
+{
+  return !find_first_in(desc, " ", [&](auto desc) {
+    auto arg_desc = cmddesc_get_args(desc);
+    if (arg_desc.empty()) {
+      return false;
+    }
+    ceph_assert(arg_desc.count("name"));
+    ceph_assert(arg_desc.count("type"));
+    auto name = arg_desc["name"];
+    auto type = arg_desc["type"];
+    if (arg_desc.count("n")) {
+      if (type == "CephInt") {
+       return !validate_arg<true, int64_t>(cct, cmdmap, arg_desc,
+                                           name, type, os);
+      } else if (type == "CephFloat") {
+       return !validate_arg<true, double>(cct, cmdmap, arg_desc,
+                                           name, type, os);
+      } else {
+       return !validate_arg<true, string>(cct, cmdmap, arg_desc,
+                                          name, type, os);
+      }
+    } else {
+      if (type == "CephInt") {
+       return !validate_arg<false, int64_t>(cct, cmdmap, arg_desc,
+                                           name, type, os);
+      } else if (type == "CephFloat") {
+       return !validate_arg<false, double>(cct, cmdmap, arg_desc,
+                                           name, type, os);
+      } else {
+       return !validate_arg<false, string>(cct, cmdmap, arg_desc,
+                                           name, type, os);
+      }
+    }
+  });
+}
+
+bool cmd_getval(CephContext *cct, const cmdmap_t& cmdmap,
+               const std::string& k, bool& val)
+{
+  /*
+   * Specialized getval for booleans.  CephBool didn't exist before Nautilus,
+   * so earlier clients are sent a CephChoices argdesc instead, and will
+   * send us a "--foo-bar" value string for boolean arguments.
+   */
+  if (cmdmap.count(k)) {
+    try {
+      val = boost::get<bool>(cmdmap.find(k)->second);
+      return true;
+    } catch (boost::bad_get&) {
+      try {
+        std::string expected = "--" + k;
+        std::replace(expected.begin(), expected.end(), '_', '-');
+
+        std::string v_str = boost::get<std::string>(cmdmap.find(k)->second);
+        if (v_str == expected) {
+          val = true;
+          return true;
+        } else {
+          throw bad_cmd_get(k, cmdmap);
+        }
+      } catch (boost::bad_get&) {
+        throw bad_cmd_get(k, cmdmap);
+      }
+    }
+  }
+  return false;
+}
+
+