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