]> git.proxmox.com Git - ceph.git/blob - ceph/src/common/cmdparse.cc
import 15.2.0 Octopus source
[ceph.git] / ceph / src / common / cmdparse.cc
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 "include/common_fwd.h"
16 #include "common/cmdparse.h"
17 #include "common/Formatter.h"
18 #include "common/debug.h"
19 #include "common/strtol.h"
20 #include "json_spirit/json_spirit.h"
21
22 /**
23 * Given a cmddesc like "foo baz name=bar,type=CephString",
24 * return the prefix "foo baz".
25 */
26 namespace TOPNSPC::common {
27 std::string cmddesc_get_prefix(const std::string_view &cmddesc)
28 {
29 string tmp(cmddesc); // FIXME: stringstream ctor can't take string_view :(
30 stringstream ss(tmp);
31 std::string word;
32 std::ostringstream result;
33 bool first = true;
34 while (std::getline(ss, word, ' ')) {
35 if (word.find_first_of(",=") != string::npos) {
36 break;
37 }
38
39 if (!first) {
40 result << " ";
41 }
42 result << word;
43 first = false;
44 }
45
46 return result.str();
47 }
48
49 using arg_desc_t = std::map<std::string_view, std::string_view>;
50
51 // Snarf up all the key=val,key=val pairs, put 'em in a dict.
52 arg_desc_t cmddesc_get_args(const string_view cmddesc)
53 {
54 arg_desc_t arg_desc;
55 for_each_substr(cmddesc, ",", [&](auto kv) {
56 // key=value; key by itself implies value is bool true
57 // name="name" means arg dict will be titled 'name'
58 auto equal = kv.find('=');
59 if (equal == kv.npos) {
60 // it should be the command
61 return;
62 }
63 auto key = kv.substr(0, equal);
64 auto val = kv.substr(equal + 1);
65 arg_desc[key] = val;
66 });
67 return arg_desc;
68 }
69
70 std::string cmddesc_get_prenautilus_compat(const std::string &cmddesc)
71 {
72 std::vector<std::string> out;
73 stringstream ss(cmddesc);
74 std::string word;
75 bool changed = false;
76 while (std::getline(ss, word, ' ')) {
77 // if no , or =, must be a plain word to put out
78 if (word.find_first_of(",=") == string::npos) {
79 out.push_back(word);
80 continue;
81 }
82 auto desckv = cmddesc_get_args(word);
83 auto j = desckv.find("type");
84 if (j != desckv.end() && j->second == "CephBool") {
85 // Instruct legacy clients or mons to send --foo-bar string in place
86 // of a 'true'/'false' value
87 std::ostringstream oss;
88 oss << std::string("--") << desckv["name"];
89 std::string val = oss.str();
90 std::replace(val.begin(), val.end(), '_', '-');
91 desckv["type"] = "CephChoices";
92 desckv["strings"] = val;
93 std::ostringstream fss;
94 for (auto k = desckv.begin(); k != desckv.end(); ++k) {
95 if (k != desckv.begin()) {
96 fss << ",";
97 }
98 fss << k->first << "=" << k->second;
99 }
100 out.push_back(fss.str());
101 changed = true;
102 } else {
103 out.push_back(word);
104 }
105 }
106 if (!changed) {
107 return cmddesc;
108 }
109 std::string o;
110 for (auto i = out.begin(); i != out.end(); ++i) {
111 if (i != out.begin()) {
112 o += " ";
113 }
114 o += *i;
115 }
116 return o;
117 }
118
119 /**
120 * Read a command description list out of cmd, and dump it to f.
121 * A signature description is a set of space-separated words;
122 * see MonCommands.h for more info.
123 */
124
125 void
126 dump_cmd_to_json(Formatter *f, uint64_t features, const string& cmd)
127 {
128 // put whole command signature in an already-opened container
129 // elements are: "name", meaning "the typeless name that means a literal"
130 // an object {} with key:value pairs representing an argument
131
132 stringstream ss(cmd);
133 std::string word;
134
135 while (std::getline(ss, word, ' ')) {
136 // if no , or =, must be a plain word to put out
137 if (word.find_first_of(",=") == string::npos) {
138 f->dump_string("arg", word);
139 continue;
140 }
141 // accumulate descriptor keywords in desckv
142 auto desckv = cmddesc_get_args(word);
143 // name the individual desc object based on the name key
144 f->open_object_section(desckv["name"]);
145
146 // Compatibility for pre-nautilus clients that don't know about CephBool
147 std::string val;
148 if (!HAVE_FEATURE(features, SERVER_NAUTILUS)) {
149 auto i = desckv.find("type");
150 if (i != desckv.end() && i->second == "CephBool") {
151 // Instruct legacy clients to send --foo-bar string in place
152 // of a 'true'/'false' value
153 std::ostringstream oss;
154 oss << std::string("--") << desckv["name"];
155 val = oss.str();
156 std::replace(val.begin(), val.end(), '_', '-');
157
158 desckv["type"] = "CephChoices";
159 desckv["strings"] = val;
160 }
161 }
162
163 // dump all the keys including name into the array
164 for (auto [key, value] : desckv) {
165 f->dump_string(key, value);
166 }
167 f->close_section(); // attribute object for individual desc
168 }
169 }
170
171 void
172 dump_cmd_and_help_to_json(Formatter *jf,
173 uint64_t features,
174 const string& secname,
175 const string& cmdsig,
176 const string& helptext)
177 {
178 jf->open_object_section(secname);
179 jf->open_array_section("sig");
180 dump_cmd_to_json(jf, features, cmdsig);
181 jf->close_section(); // sig array
182 jf->dump_string("help", helptext);
183 jf->close_section(); // cmd
184 }
185
186 void
187 dump_cmddesc_to_json(Formatter *jf,
188 uint64_t features,
189 const string& secname,
190 const string& cmdsig,
191 const string& helptext,
192 const string& module,
193 const string& perm,
194 uint64_t flags)
195 {
196 jf->open_object_section(secname);
197 jf->open_array_section("sig");
198 dump_cmd_to_json(jf, features, cmdsig);
199 jf->close_section(); // sig array
200 jf->dump_string("help", helptext);
201 jf->dump_string("module", module);
202 jf->dump_string("perm", perm);
203 jf->dump_int("flags", flags);
204 jf->close_section(); // cmd
205 }
206
207 void cmdmap_dump(const cmdmap_t &cmdmap, Formatter *f)
208 {
209 ceph_assert(f != nullptr);
210
211 class dump_visitor : public boost::static_visitor<void>
212 {
213 Formatter *f;
214 std::string const &key;
215 public:
216 dump_visitor(Formatter *f_, std::string const &key_)
217 : f(f_), key(key_)
218 {
219 }
220
221 void operator()(const std::string &operand) const
222 {
223 f->dump_string(key, operand);
224 }
225
226 void operator()(const bool &operand) const
227 {
228 f->dump_bool(key, operand);
229 }
230
231 void operator()(const int64_t &operand) const
232 {
233 f->dump_int(key, operand);
234 }
235
236 void operator()(const double &operand) const
237 {
238 f->dump_float(key, operand);
239 }
240
241 void operator()(const std::vector<std::string> &operand) const
242 {
243 f->open_array_section(key);
244 for (const auto& i : operand) {
245 f->dump_string("item", i);
246 }
247 f->close_section();
248 }
249
250 void operator()(const std::vector<int64_t> &operand) const
251 {
252 f->open_array_section(key);
253 for (const auto i : operand) {
254 f->dump_int("item", i);
255 }
256 f->close_section();
257 }
258
259 void operator()(const std::vector<double> &operand) const
260 {
261 f->open_array_section(key);
262 for (const auto i : operand) {
263 f->dump_float("item", i);
264 }
265 f->close_section();
266 }
267 };
268
269 //f->open_object_section("cmdmap");
270 for (const auto &i : cmdmap) {
271 boost::apply_visitor(dump_visitor(f, i.first), i.second);
272 }
273 //f->close_section();
274 }
275
276
277 /** Parse JSON in vector cmd into a map from field to map of values
278 * (use mValue/mObject)
279 * 'cmd' should not disappear over lifetime of map
280 * 'mapp' points to the caller's map
281 * 'ss' captures any errors during JSON parsing; if function returns
282 * false, ss is valid */
283
284 bool
285 cmdmap_from_json(const vector<string>& cmd, cmdmap_t *mapp, stringstream &ss)
286 {
287 json_spirit::mValue v;
288
289 string fullcmd;
290 // First, join all cmd strings
291 for (auto& c : cmd)
292 fullcmd += c;
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(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 {
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(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 }