1    	// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- 
2    	// vim: ts=8 sw=2 smarttab
3    	/*
4    	 * Ceph - scalable distributed file system
5    	 *
6    	 * Copyright (C) 2013 Inktank Storage, Inc.
7    	 *
8    	 * This is free software; you can redistribute it and/or
9    	 * modify it under the terms of the GNU General Public
10   	 * License version 2, as published by the Free Software
11   	 * Foundation.  See file COPYING.
12   	 *
13   	 */
14   	
15   	#include "common/cmdparse.h"
16   	#include "common/Formatter.h"
17   	#include "common/debug.h"
18   	#include "common/strtol.h"
19   	#include "json_spirit/json_spirit.h"
20   	
21   	/**
22   	 * Given a cmddesc like "foo baz name=bar,type=CephString",
23   	 * return the prefix "foo baz".
24   	 */
25   	std::string cmddesc_get_prefix(const std::string_view &cmddesc)
26   	{
27   	  string tmp(cmddesc); // FIXME: stringstream ctor can't take string_view :(
28   	  stringstream ss(tmp);
29   	  std::string word;
30   	  std::ostringstream result;
31   	  bool first = true;
32   	  while (std::getline(ss, word, ' ')) {
33   	    if (word.find_first_of(",=") != string::npos) {
34   	      break;
35   	    }
36   	
37   	    if (!first) {
38   	      result << " ";
39   	    }
40   	    result << word;
41   	    first = false;
42   	  }
43   	
44   	  return result.str();
45   	}
46   	
47   	using arg_desc_t = std::map<std::string_view, std::string_view>;
48   	
49   	// Snarf up all the key=val,key=val pairs, put 'em in a dict.
50   	template<class String>
51   	arg_desc_t cmddesc_get_args(const String& cmddesc)
52   	{
53   	  arg_desc_t arg_desc;
54   	  for_each_substr(cmddesc, ",", [&](auto kv) {
55   	      // key=value; key by itself implies value is bool true
56   	      // name="name" means arg dict will be titled 'name'
57   	      auto equal = kv.find('=');
58   	      if (equal == kv.npos) {
59   		// it should be the command
60   		return;
61   	      }
62   	      auto key = kv.substr(0, equal);
63   	      auto val = kv.substr(equal + 1);
64   	      arg_desc[key] = val;
65   	    });
66   	  return arg_desc;
67   	}
68   	
69   	std::string cmddesc_get_prenautilus_compat(const std::string &cmddesc)
70   	{
71   	  std::vector<std::string> out;
72   	  stringstream ss(cmddesc);
73   	  std::string word;
74   	  bool changed = false;
75   	  while (std::getline(ss, word, ' ')) {
76   	    // if no , or =, must be a plain word to put out
77   	    if (word.find_first_of(",=") == string::npos) {
78   	      out.push_back(word);
79   	      continue;
80   	    }
81   	    auto desckv = cmddesc_get_args(word);
82   	    auto j = desckv.find("type");
83   	    if (j != desckv.end() && j->second == "CephBool") {
84   	      // Instruct legacy clients or mons to send --foo-bar string in place
85   	      // of a 'true'/'false' value
86   	      std::ostringstream oss;
87   	      oss << std::string("--") << desckv["name"];
88   	      std::string val = oss.str();
89   	      std::replace(val.begin(), val.end(), '_', '-');
90   	      desckv["type"] = "CephChoices";
91   	      desckv["strings"] = val;
92   	      std::ostringstream fss;
93   	      for (auto k = desckv.begin(); k != desckv.end(); ++k) {
94   		if (k != desckv.begin()) {
95   		  fss << ",";
96   		}
97   		fss << k->first << "=" << k->second;
98   	      }
99   	      out.push_back(fss.str());
100  	      changed = true;
101  	    } else {
102  	      out.push_back(word);
103  	    }
104  	  }
105  	  if (!changed) {
106  	    return cmddesc;
107  	  }
108  	  std::string o;
109  	  for (auto i = out.begin(); i != out.end(); ++i) {
110  	    if (i != out.begin()) {
111  	      o += " ";
112  	    }
113  	    o += *i;
114  	  }
115  	  return o;
116  	}
117  	
118  	/**
119  	 * Read a command description list out of cmd, and dump it to f.
120  	 * A signature description is a set of space-separated words;
121  	 * see MonCommands.h for more info.
122  	 */
123  	
124  	void
125  	dump_cmd_to_json(Formatter *f, uint64_t features, const string& cmd)
126  	{
127  	  // put whole command signature in an already-opened container
128  	  // elements are: "name", meaning "the typeless name that means a literal"
129  	  // an object {} with key:value pairs representing an argument
130  	
131  	  stringstream ss(cmd);
132  	  std::string word;
133  	
134  	  while (std::getline(ss, word, ' ')) {
135  	    // if no , or =, must be a plain word to put out
136  	    if (word.find_first_of(",=") == string::npos) {
137  	      f->dump_string("arg", word);
138  	      continue;
139  	    }
140  	    // accumulate descriptor keywords in desckv
141  	    auto desckv = cmddesc_get_args(word);
142  	    // name the individual desc object based on the name key
143  	    f->open_object_section(string(desckv["name"]).c_str());
144  	
145  	    // Compatibility for pre-nautilus clients that don't know about CephBool
146  	    std::string val;
147  	    if (!HAVE_FEATURE(features, SERVER_NAUTILUS)) {
148  	      auto i = desckv.find("type");
149  	      if (i != desckv.end() && i->second == "CephBool") {
150  	        // Instruct legacy clients to send --foo-bar string in place
151  	        // of a 'true'/'false' value
152  	        std::ostringstream oss;
153  	        oss << std::string("--") << desckv["name"];
154  	        val = oss.str();
155  	        std::replace(val.begin(), val.end(), '_', '-');
156  	
157  	        desckv["type"] = "CephChoices";
158  	        desckv["strings"] = val;
159  	      }
160  	    }
161  	
162  	    // dump all the keys including name into the array
163  	    for (auto [key, value] : desckv) {
164  	      f->dump_string(string(key).c_str(), string(value));
165  	    }
166  	    f->close_section(); // attribute object for individual desc
167  	  }
168  	}
169  	
170  	void
171  	dump_cmd_and_help_to_json(Formatter *jf,
172  				  uint64_t features,
173  				  const string& secname,
174  				  const string& cmdsig,
175  				  const string& helptext)
176  	{
177  	      jf->open_object_section(secname.c_str());
178  	      jf->open_array_section("sig");
179  	      dump_cmd_to_json(jf, features, cmdsig);
180  	      jf->close_section(); // sig array
181  	      jf->dump_string("help", helptext.c_str());
182  	      jf->close_section(); // cmd
183  	}
184  	
185  	void
186  	dump_cmddesc_to_json(Formatter *jf,
187  			     uint64_t features,
188  			     const string& secname,
189  			     const string& cmdsig,
190  			     const string& helptext,
191  			     const string& module,
192  			     const string& perm,
193  			     uint64_t flags)
194  	{
195  	      jf->open_object_section(secname.c_str());
196  	      jf->open_array_section("sig");
197  	      dump_cmd_to_json(jf, features, cmdsig);
198  	      jf->close_section(); // sig array
199  	      jf->dump_string("help", helptext.c_str());
200  	      jf->dump_string("module", module.c_str());
201  	      jf->dump_string("perm", perm.c_str());
202  	      jf->dump_int("flags", flags);
203  	      jf->close_section(); // cmd
204  	}
205  	
206  	void cmdmap_dump(const cmdmap_t &cmdmap, Formatter *f)
207  	{
208  	  ceph_assert(f != nullptr);
209  	
210  	  class dump_visitor : public boost::static_visitor<void>
211  	  {
212  	    Formatter *f;
213  	    std::string const &key;
214  	    public:
215  	    dump_visitor(Formatter *f_, std::string const &key_)
216  	      : f(f_), key(key_)
217  	    {
218  	    }
219  	
220  	    void operator()(const std::string &operand) const
221  	    {
222  	      f->dump_string(key.c_str(), operand);
223  	    }
224  	
225  	    void operator()(const bool &operand) const
226  	    {
227  	      f->dump_bool(key.c_str(), operand);
228  	    }
229  	
230  	    void operator()(const int64_t &operand) const
231  	    {
232  	      f->dump_int(key.c_str(), operand);
233  	    }
234  	
235  	    void operator()(const double &operand) const
236  	    {
237  	      f->dump_float(key.c_str(), operand);
238  	    }
239  	
240  	    void operator()(const std::vector<std::string> &operand) const
241  	    {
242  	      f->open_array_section(key.c_str());
243  	      for (const auto i : operand) {
244  	        f->dump_string("item", i);
245  	      }
246  	      f->close_section();
247  	    }
248  	
249  	    void operator()(const std::vector<int64_t> &operand) const
250  	    {
251  	      f->open_array_section(key.c_str());
252  	      for (const auto i : operand) {
253  	        f->dump_int("item", i);
254  	      }
255  	      f->close_section();
256  	    }
257  	
258  	    void operator()(const std::vector<double> &operand) const
259  	    {
260  	      f->open_array_section(key.c_str());
261  	      for (const auto i : operand) {
262  	        f->dump_float("item", i);
263  	      }
264  	      f->close_section();
265  	    }
266  	  };
267  	
268  	  //f->open_object_section("cmdmap");
269  	  for (const auto &i : cmdmap) {
270  	    boost::apply_visitor(dump_visitor(f, i.first), i.second);
271  	  }
272  	  //f->close_section();
273  	}
274  	
275  	
276  	/** Parse JSON in vector cmd into a map from field to map of values
277  	 * (use mValue/mObject)
278  	 * 'cmd' should not disappear over lifetime of map
279  	 * 'mapp' points to the caller's map
280  	 * 'ss' captures any errors during JSON parsing; if function returns
281  	 * false, ss is valid */
282  	
283  	bool
284  	cmdmap_from_json(vector<string> cmd, cmdmap_t *mapp, stringstream &ss)
285  	{
286  	  json_spirit::mValue v;
287  	
288  	  string fullcmd;
289  	  // First, join all cmd strings
290  	  for (vector<string>::iterator it = cmd.begin();
291  	       it != cmd.end(); ++it)
292  	    fullcmd += *it;
293  	
294  	  try {
295  	    if (!json_spirit::read(fullcmd, v))
296  	      throw runtime_error("unparseable JSON " + fullcmd);
297  	    if (v.type() != json_spirit::obj_type)
298  	      throw(runtime_error("not JSON object " + fullcmd));
299  	
300  	    // allocate new mObject (map) to return
301  	    // make sure all contents are simple types (not arrays or objects)
302  	    json_spirit::mObject o = v.get_obj();
303  	    for (map<string, json_spirit::mValue>::iterator it = o.begin();
304  		 it != o.end(); ++it) {
305  	
306  	      // ok, marshal it into our string->cmd_vartype map, or throw an
307  	      // exception if it's not a simple datatype.  This is kind of
308  	      // annoying, since json_spirit has a boost::variant inside it
309  	      // already, but it's not public.  Oh well.
310  	
311  	      switch (it->second.type()) {
312  	
313  	      case json_spirit::obj_type:
314  	      default:
315  		throw(runtime_error("JSON array/object not allowed " + fullcmd));
316  	        break;
317  	
318  	      case json_spirit::array_type:
319  		{
320  		  // array is a vector of values.  Unpack it to a vector
321  		  // of strings, doubles, or int64_t, the only types we handle.
322  		  const vector<json_spirit::mValue>& spvals = it->second.get_array();
323  		  if (spvals.empty()) {
324  		    // if an empty array is acceptable, the caller should always check for
325  		    // vector<string> if the expected value of "vector<int64_t>" in the
326  		    // cmdmap is missing.
327  		    (*mapp)[it->first] = vector<string>();
328  		  } else if (spvals.front().type() == json_spirit::str_type) {
329  		    vector<string> outv;
330  		    for (const auto& sv : spvals) {
331  		      if (sv.type() != json_spirit::str_type) {
332  			throw(runtime_error("Can't handle arrays of multiple types"));
333  		      }
334  		      outv.push_back(sv.get_str());
335  		    }
336  		    (*mapp)[it->first] = std::move(outv);
337  		  } else if (spvals.front().type() == json_spirit::int_type) {
338  		    vector<int64_t> outv;
339  		    for (const auto& sv : spvals) {
340  		      if (spvals.front().type() != json_spirit::int_type) {
341  			throw(runtime_error("Can't handle arrays of multiple types"));
342  		      }
343  		      outv.push_back(sv.get_int64());
344  		    }
345  		    (*mapp)[it->first] = std::move(outv);
346  		  } else if (spvals.front().type() == json_spirit::real_type) {
347  		    vector<double> outv;
348  		    for (const auto& sv : spvals) {
349  		      if (spvals.front().type() != json_spirit::real_type) {
350  			throw(runtime_error("Can't handle arrays of multiple types"));
351  		      }
352  		      outv.push_back(sv.get_real());
353  		    }
354  		    (*mapp)[it->first] = std::move(outv);
355  		  } else {
356  		    throw(runtime_error("Can't handle arrays of types other than "
357  					"int, string, or double"));
358  		  }
359  		}
360  		break;
361  	      case json_spirit::str_type:
362  		(*mapp)[it->first] = it->second.get_str();
363  		break;
364  	
365  	      case json_spirit::bool_type:
366  		(*mapp)[it->first] = it->second.get_bool();
367  		break;
368  	
369  	      case json_spirit::int_type:
370  		(*mapp)[it->first] = it->second.get_int64();
371  		break;
372  	
373  	      case json_spirit::real_type:
374  		(*mapp)[it->first] = it->second.get_real();
375  		break;
376  	      }
377  	    }
378  	    return true;
379  	  } catch (runtime_error &e) {
380  	    ss << e.what();
381  	    return false;
382  	  }
383  	}
384  	
385  	class stringify_visitor : public boost::static_visitor<string>
386  	{
387  	  public:
388  	    template <typename T>
389  	    string operator()(T &operand) const
390  	      {
391  		ostringstream oss;
392  		oss << operand;
393  		return oss.str();
394  	      }
395  	};
396  	
397  	string 
398  	cmd_vartype_stringify(const cmd_vartype &v)
399  	{
400  	  return boost::apply_visitor(stringify_visitor(), v);
401  	}
402  	
403  	
404  	void
405  	handle_bad_get(CephContext *cct, const string& k, const char *tname)
406  	{
407  	  ostringstream errstr;
408  	  int status;
409  	  const char *typestr = abi::__cxa_demangle(tname, 0, 0, &status);
410  	  if (status != 0) 
411  	    typestr = tname;
412  	  errstr << "bad boost::get: key " << k << " is not type " << typestr;
413  	  lderr(cct) << errstr.str() << dendl;
414  	
415  	  ostringstream oss;
416  	  oss << BackTrace(1);
417  	  lderr(cct) << oss.str() << dendl;
418  	
419  	  if (status == 0)
420  	    free((char *)typestr);
421  	}
422  	
423  	long parse_pos_long(const char *s, std::ostream *pss)
424  	{
425  	  if (*s == '-' || *s == '+') {
426  	    if (pss)
427  	      *pss << "expected numerical value, got: " << s;
428  	    return -EINVAL;
429  	  }
430  	
431  	  string err;
432  	  long r = strict_strtol(s, 10, &err);
433  	  if ((r == 0) && !err.empty()) {
434  	    if (pss)
435  	      *pss << err;
436  	    return -1;
437  	  }
438  	  if (r < 0) {
439  	    if (pss)
440  	      *pss << "unable to parse positive integer '" << s << "'";
441  	    return -1;
442  	  }
443  	  return r;
444  	}
445  	
446  	int parse_osd_id(const char *s, std::ostream *pss)
447  	{
448  	  // osd.NNN?
449  	  if (strncmp(s, "osd.", 4) == 0) {
450  	    s += 4;
451  	  }
452  	
453  	  // NNN?
454  	  ostringstream ss;
455  	  long id = parse_pos_long(s, &ss);
456  	  if (id < 0) {
457  	    *pss << ss.str();
458  	    return id;
459  	  }
460  	  if (id > 0xffff) {
461  	    *pss << "osd id " << id << " is too large";
462  	    return -ERANGE;
463  	  }
464  	  return id;
465  	}
466  	
467  	namespace {
468  	template <typename Func>
469  	bool find_first_in(std::string_view s, const char *delims, Func&& f)
470  	{
471  	  auto pos = s.find_first_not_of(delims);
472  	  while (pos != s.npos) {
473  	    s.remove_prefix(pos);
474  	    auto end = s.find_first_of(delims);
475  	    if (f(s.substr(0, end))) {
476  	      return true;
477  	    }
478  	    pos = s.find_first_not_of(delims, end);
479  	  }
480  	  return false;
481  	}
482  	
483  	template<typename T>
484  	T str_to_num(const std::string& s)
485  	{
486  	  if constexpr (is_same_v<T, int>) {
487  	    return std::stoi(s);
488  	  } else if constexpr (is_same_v<T, long>) {
489  	    return std::stol(s);
490  	  } else if constexpr (is_same_v<T, long long>) {
491  	    return std::stoll(s);
492  	  } else if constexpr (is_same_v<T, double>) {
493  	    return std::stod(s);
494  	  }
495  	}
496  	
497  	template<typename T>
498  	bool arg_in_range(T value, const arg_desc_t& desc, std::ostream& os) {
499  	  auto range = desc.find("range");
500  	  if (range == desc.end()) {
501  	    return true;
502  	  }
503  	  auto min_max = get_str_list(string(range->second), "|");
504  	  auto min = str_to_num<T>(min_max.front());
505  	  auto max = numeric_limits<T>::max();
506  	  if (min_max.size() > 1) {
507  	    max = str_to_num<T>(min_max.back());
508  	  }
509  	  if (value < min || value > max) {
510  	    os << "'" << value << "' out of range: " << min_max;
511  	    return false;
512  	  }
513  	  return true;
514  	}
515  	
516  	bool validate_str_arg(std::string_view value,
517  			      std::string_view type,
518  			      const arg_desc_t& desc,
519  			      std::ostream& os)
520  	{
521  	  if (type == "CephIPAddr") {
522  	    entity_addr_t addr;
523  	    if (addr.parse(string(value).c_str())) {
524  	      return true;
525  	    } else {
526  	      os << "failed to parse addr '" << value << "', should be ip:[port]";
527  	      return false;
528  	    }
529  	  } else if (type == "CephChoices") {
530  	    auto choices = desc.find("strings");
531  	    ceph_assert(choices != end(desc));
532  	    auto strings = choices->second;
533  	    if (find_first_in(strings, "|", [=](auto choice) {
534  		  return (value == choice);
535  		})) {
536  	      return true;
537  	    } else {
538  	      os << "'" << value << "' not belong to '" << strings << "'";
539  	      return false;
540  	    }
541  	  } else {
542  	    // CephString or other types like CephPgid
543  	    return true;
544  	  }
545  	}
546  	
547  	template<bool is_vector,
548  		 typename T,
549  		 typename Value = conditional_t<is_vector,
550  						vector<T>,
551  						T>>
552  	bool validate_arg(CephContext* cct,
553  			  const cmdmap_t& cmdmap,
554  			  const arg_desc_t& desc,
555  			  const std::string_view name,
556  			  const std::string_view type,
557  			  std::ostream& os)
558  	{
559  	  Value v;
560  	  try {
561  	    if (!cmd_getval(cct, cmdmap, string(name), v)) {
562  	      if constexpr (is_vector) {
563  		  // an empty list is acceptable.
564  		  return true;
565  		} else {
566  		if (auto req = desc.find("req");
567  		    req != end(desc) && req->second == "false") {
568  		  return true;
569  		} else {
570  		  os << "missing required parameter: '" << name << "'";
571  		  return false;
572  		}
573  	      }
574  	    }
575  	  } catch (const bad_cmd_get& e) {
576  	    return false;
577  	  }
578  	  auto validate = [&](const T& value) {
579  	    if constexpr (is_same_v<std::string, T>) {
580  	      return validate_str_arg(value, type, desc, os);
581  	    } else if constexpr (is_same_v<int64_t, T> ||
582  				 is_same_v<double, T>) {
583  	      return arg_in_range(value, desc, os);
584  	    }
585  	  };
586  	  if constexpr(is_vector) {
587  	    return find_if_not(begin(v), end(v), validate) == end(v);
588  	  } else {
589  	    return validate(v);
590  	  }
591  	}
592  	} // anonymous namespace
593  	
594  	bool validate_cmd(CephContext* cct,
595  			  const std::string& desc,
596  			  const cmdmap_t& cmdmap,
597  			  std::ostream& os)
598  	{
(1) Event parameter_hidden: declaration hides parameter "desc" (declared at line 595)
(2) Event caretline: ^
599  	  return !find_first_in(desc, " ", [&](auto desc) {
600  	    auto arg_desc = cmddesc_get_args(desc);
601  	    if (arg_desc.empty()) {
602  	      return false;
603  	    }
604  	    ceph_assert(arg_desc.count("name"));
605  	    ceph_assert(arg_desc.count("type"));
606  	    auto name = arg_desc["name"];
607  	    auto type = arg_desc["type"];
608  	    if (arg_desc.count("n")) {
609  	      if (type == "CephInt") {
610  		return !validate_arg<true, int64_t>(cct, cmdmap, arg_desc,
611  						    name, type, os);
612  	      } else if (type == "CephFloat") {
613  		return !validate_arg<true, double>(cct, cmdmap, arg_desc,
614  						    name, type, os);
615  	      } else {
616  		return !validate_arg<true, string>(cct, cmdmap, arg_desc,
617  						   name, type, os);
618  	      }
619  	    } else {
620  	      if (type == "CephInt") {
621  		return !validate_arg<false, int64_t>(cct, cmdmap, arg_desc,
622  						    name, type, os);
623  	      } else if (type == "CephFloat") {
624  		return !validate_arg<false, double>(cct, cmdmap, arg_desc,
625  						    name, type, os);
626  	      } else {
627  		return !validate_arg<false, string>(cct, cmdmap, arg_desc,
628  						    name, type, os);
629  	      }
630  	    }
631  	  });
632  	}
633  	
634  	bool cmd_getval(CephContext *cct, const cmdmap_t& cmdmap,
635  			const std::string& k, bool& val)
636  	{
637  	  /*
638  	   * Specialized getval for booleans.  CephBool didn't exist before Nautilus,
639  	   * so earlier clients are sent a CephChoices argdesc instead, and will
640  	   * send us a "--foo-bar" value string for boolean arguments.
641  	   */
642  	  if (cmdmap.count(k)) {
643  	    try {
644  	      val = boost::get<bool>(cmdmap.find(k)->second);
645  	      return true;
646  	    } catch (boost::bad_get&) {
647  	      try {
648  	        std::string expected = "--" + k;
649  	        std::replace(expected.begin(), expected.end(), '_', '-');
650  	
651  	        std::string v_str = boost::get<std::string>(cmdmap.find(k)->second);
652  	        if (v_str == expected) {
653  	          val = true;
654  	          return true;
655  	        } else {
656  	          throw bad_cmd_get(k, cmdmap);
657  	        }
658  	      } catch (boost::bad_get&) {
659  	        throw bad_cmd_get(k, cmdmap);
660  	      }
661  	    }
662  	  }
663  	  return false;
664  	}
665  	
666  	
667