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