]>
git.proxmox.com Git - ceph.git/blob - 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
4 * Ceph - scalable distributed file system
6 * Copyright (C) 2013 Inktank Storage, Inc.
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.
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"
23 using std::ostringstream
;
25 using std::stringstream
;
26 using std::string_view
;
29 using namespace std::literals
;
32 * Given a cmddesc like "foo baz name=bar,type=CephString",
33 * return the prefix "foo baz".
35 namespace TOPNSPC::common
{
36 std::string
cmddesc_get_prefix(const std::string_view
&cmddesc
)
38 string
tmp(cmddesc
); // FIXME: stringstream ctor can't take string_view :(
41 std::ostringstream result
;
43 while (std::getline(ss
, word
, ' ')) {
44 if (word
.find_first_of(",=") != string::npos
) {
58 using arg_desc_t
= std::map
<std::string_view
, std::string_view
>;
60 // Snarf up all the key=val,key=val pairs, put 'em in a dict.
61 arg_desc_t
cmddesc_get_args(const string_view cmddesc
)
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
72 auto key
= kv
.substr(0, equal
);
73 auto val
= kv
.substr(equal
+ 1);
79 std::string
cmddesc_get_prenautilus_compat(const std::string
&cmddesc
)
81 std::vector
<std::string
> out
;
82 stringstream
ss(cmddesc
);
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
) {
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
;
97 oss
<< "--" << desckv
["name"];
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()) {
107 fss
<< k
->first
<< "=" << k
->second
;
109 out
.push_back(fss
.str());
119 for (auto i
= out
.begin(); i
!= out
.end(); ++i
) {
120 if (i
!= out
.begin()) {
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.
135 dump_cmd_to_json(Formatter
*f
, uint64_t features
, const string
& cmd
)
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
141 stringstream
ss(cmd
);
143 bool positional
= true;
145 while (std::getline(ss
, word
, ' ')) {
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
);
157 // accumulate descriptor keywords in desckv
158 auto desckv
= cmddesc_get_args(word
);
159 // name the individual desc object based on the name key
160 f
->open_object_section(desckv
["name"]);
162 // Compatibility for pre-nautilus clients that don't know about CephBool
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
;
170 oss
<< "--" << desckv
["name"];
172 std::replace(val
.begin(), val
.end(), '_', '-');
174 desckv
["type"] = "CephChoices";
175 desckv
["strings"] = val
;
179 // dump all the keys including name into the array
181 desckv
["positional"] = "false";
183 for (auto [key
, value
] : desckv
) {
184 if (key
== "positional") {
185 if (!HAVE_FEATURE(features
, SERVER_QUINCY
)) {
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");
192 f
->dump_string(key
, value
);
195 f
->close_section(); // attribute object for individual desc
200 dump_cmd_and_help_to_json(Formatter
*jf
,
202 const string
& secname
,
203 const string
& cmdsig
,
204 const string
& helptext
)
206 jf
->open_object_section(secname
);
207 jf
->open_array_section("sig");
208 dump_cmd_to_json(jf
, features
, cmdsig
);
209 jf
->close_section(); // sig array
210 jf
->dump_string("help", helptext
);
211 jf
->close_section(); // cmd
215 dump_cmddesc_to_json(Formatter
*jf
,
217 const string
& secname
,
218 const string
& cmdsig
,
219 const string
& helptext
,
220 const string
& module
,
224 jf
->open_object_section(secname
);
225 jf
->open_array_section("sig");
226 dump_cmd_to_json(jf
, features
, cmdsig
);
227 jf
->close_section(); // sig array
228 jf
->dump_string("help", helptext
);
229 jf
->dump_string("module", module
);
230 jf
->dump_string("perm", perm
);
231 jf
->dump_int("flags", flags
);
232 jf
->close_section(); // cmd
235 void cmdmap_dump(const cmdmap_t
&cmdmap
, Formatter
*f
)
237 ceph_assert(f
!= nullptr);
239 class dump_visitor
: public boost::static_visitor
<void>
242 std::string
const &key
;
244 dump_visitor(Formatter
*f_
, std::string
const &key_
)
249 void operator()(const std::string
&operand
) const
251 f
->dump_string(key
, operand
);
254 void operator()(const bool &operand
) const
256 f
->dump_bool(key
, operand
);
259 void operator()(const int64_t &operand
) const
261 f
->dump_int(key
, operand
);
264 void operator()(const double &operand
) const
266 f
->dump_float(key
, operand
);
269 void operator()(const std::vector
<std::string
> &operand
) const
271 f
->open_array_section(key
);
272 for (const auto& i
: operand
) {
273 f
->dump_string("item", i
);
278 void operator()(const std::vector
<int64_t> &operand
) const
280 f
->open_array_section(key
);
281 for (const auto i
: operand
) {
282 f
->dump_int("item", i
);
287 void operator()(const std::vector
<double> &operand
) const
289 f
->open_array_section(key
);
290 for (const auto i
: operand
) {
291 f
->dump_float("item", i
);
297 //f->open_object_section("cmdmap");
298 for (const auto &i
: cmdmap
) {
299 boost::apply_visitor(dump_visitor(f
, i
.first
), i
.second
);
301 //f->close_section();
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 */
313 cmdmap_from_json(const vector
<string
>& cmd
, cmdmap_t
*mapp
, std::ostream
& ss
)
315 json_spirit::mValue v
;
318 // First, join all cmd strings
323 if (!json_spirit::read(fullcmd
, v
))
324 throw std::runtime_error("unparseable JSON " + fullcmd
);
325 if (v
.type() != json_spirit::obj_type
)
326 throw std::runtime_error("not JSON object " + fullcmd
);
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();
331 for (auto it
= o
.begin(); it
!= o
.end(); ++it
) {
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.
338 switch (it
->second
.type()) {
340 case json_spirit::obj_type
:
342 throw std::runtime_error("JSON array/object not allowed " + fullcmd
);
345 case json_spirit::array_type
:
347 // array is a vector of values. Unpack it to a vector
348 // of strings, doubles, or int64_t, the only types we handle.
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
) {
357 for (const auto& sv
: spvals
) {
358 if (sv
.type() != json_spirit::str_type
) {
359 throw std::runtime_error("Can't handle arrays of multiple types");
361 outv
.push_back(sv
.get_str());
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
) {
368 throw std::runtime_error("Can't handle arrays of multiple types");
370 outv
.push_back(sv
.get_int64());
372 (*mapp
)[it
->first
] = std::move(outv
);
373 } else if (spvals
.front().type() == json_spirit::real_type
) {
375 for (const auto& sv
: spvals
) {
376 if (spvals
.front().type() != json_spirit::real_type
) {
377 throw std::runtime_error("Can't handle arrays of multiple types");
379 outv
.push_back(sv
.get_real());
381 (*mapp
)[it
->first
] = std::move(outv
);
383 throw std::runtime_error("Can't handle arrays of types other than "
384 "int, string, or double");
388 case json_spirit::str_type
:
389 (*mapp
)[it
->first
] = it
->second
.get_str();
392 case json_spirit::bool_type
:
393 (*mapp
)[it
->first
] = it
->second
.get_bool();
396 case json_spirit::int_type
:
397 (*mapp
)[it
->first
] = it
->second
.get_int64();
400 case json_spirit::real_type
:
401 (*mapp
)[it
->first
] = it
->second
.get_real();
406 } catch (const std::runtime_error
&e
) {
412 class stringify_visitor
: public boost::static_visitor
<string
>
415 template <typename T
>
416 string
operator()(T
&operand
) const
425 cmd_vartype_stringify(const cmd_vartype
&v
)
427 return boost::apply_visitor(stringify_visitor(), v
);
432 handle_bad_get(CephContext
*cct
, const string
& k
, const char *tname
)
434 ostringstream errstr
;
436 const char *typestr
= abi::__cxa_demangle(tname
, 0, 0, &status
);
439 errstr
<< "bad boost::get: key " << k
<< " is not type " << typestr
;
440 lderr(cct
) << errstr
.str() << dendl
;
443 oss
<< ClibBackTrace(1);
444 lderr(cct
) << oss
.str() << dendl
;
447 free((char *)typestr
);
450 long parse_pos_long(const char *s
, std::ostream
*pss
)
452 if (*s
== '-' || *s
== '+') {
454 *pss
<< "expected numerical value, got: " << s
;
459 long r
= strict_strtol(s
, 10, &err
);
460 if ((r
== 0) && !err
.empty()) {
467 *pss
<< "unable to parse positive integer '" << s
<< "'";
473 int parse_osd_id(const char *s
, std::ostream
*pss
)
476 if (strncmp(s
, "osd.", 4) == 0) {
482 long id
= parse_pos_long(s
, &ss
);
488 *pss
<< "osd id " << id
<< " is too large";
495 template <typename Func
>
496 bool find_first_in(std::string_view s
, const char *delims
, Func
&& f
)
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
))) {
505 pos
= s
.find_first_not_of(delims
, end
);
511 T
str_to_num(const std::string
& s
)
513 if constexpr (is_same_v
<T
, int>) {
515 } else if constexpr (is_same_v
<T
, long>) {
517 } else if constexpr (is_same_v
<T
, long long>) {
518 return std::stoll(s
);
519 } else if constexpr (is_same_v
<T
, double>) {
525 bool arg_in_range(T value
, const arg_desc_t
& desc
, std::ostream
& os
) {
526 auto range
= desc
.find("range");
527 if (range
== desc
.end()) {
530 auto min_max
= get_str_list(string(range
->second
), "|");
531 auto min
= str_to_num
<T
>(min_max
.front());
532 auto max
= std::numeric_limits
<T
>::max();
533 if (min_max
.size() > 1) {
534 max
= str_to_num
<T
>(min_max
.back());
536 if (value
< min
|| value
> max
) {
537 os
<< "'" << value
<< "' out of range: " << min_max
;
543 bool validate_str_arg(std::string_view value
,
544 std::string_view type
,
545 const arg_desc_t
& desc
,
548 if (type
== "CephIPAddr") {
550 if (addr
.parse(value
)) {
553 os
<< "failed to parse addr '" << value
<< "', should be ip:[port]";
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
);
565 os
<< "'" << value
<< "' not belong to '" << strings
<< "'";
569 // CephString or other types like CephPgid
574 bool 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
,
583 if (!cmd_getval(cmdmap
, name
, v
)) {
584 if (auto req
= desc
.find("req");
585 req
!= end(desc
) && req
->second
== "false") {
588 os
<< "missing required parameter: '" << name
<< "'";
593 } catch (const bad_cmd_get
& e
) {
598 template<bool is_vector
,
600 typename Value
= std::conditional_t
<is_vector
,
603 bool 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
,
612 if (!cmd_getval(cmdmap
, name
, v
)) {
613 if constexpr (is_vector
) {
614 // an empty list is acceptable.
617 if (auto req
= desc
.find("req");
618 req
!= end(desc
) && req
->second
== "false") {
621 os
<< "missing required parameter: '" << name
<< "'";
626 } catch (const bad_cmd_get
& e
) {
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
);
637 if constexpr(is_vector
) {
638 return find_if_not(begin(v
), end(v
), validate
) == end(v
);
643 } // anonymous namespace
645 bool validate_cmd(CephContext
* cct
,
646 const std::string
& desc
,
647 const cmdmap_t
& cmdmap
,
650 return !find_first_in(desc
, " ", [&](auto desc
) {
651 auto arg_desc
= cmddesc_get_args(desc
);
652 if (arg_desc
.empty()) {
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
,
663 } else if (type
== "CephFloat") {
664 return !validate_arg
<true, double>(cct
, cmdmap
, arg_desc
,
667 return !validate_arg
<true, string
>(cct
, cmdmap
, arg_desc
,
671 if (type
== "CephInt") {
672 return !validate_arg
<false, int64_t>(cct
, cmdmap
, arg_desc
,
674 } else if (type
== "CephFloat") {
675 return !validate_arg
<false, double>(cct
, cmdmap
, arg_desc
,
677 } else if (type
== "CephBool") {
678 return !validate_bool(cct
, cmdmap
, arg_desc
,
681 return !validate_arg
<false, string
>(cct
, cmdmap
, arg_desc
,
688 bool cmd_getval(const cmdmap_t
& cmdmap
,
689 std::string_view k
, bool& val
)
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.
696 auto found
= cmdmap
.find(k
);
697 if (found
== cmdmap
.end()) {
701 val
= boost::get
<bool>(found
->second
);
703 } catch (boost::bad_get
&) {
705 std::string expected
{"--"};
707 std::replace(expected
.begin(), expected
.end(), '_', '-');
709 std::string v_str
= boost::get
<std::string
>(found
->second
);
710 if (v_str
== expected
) {
714 throw bad_cmd_get(k
, cmdmap
);
716 } catch (boost::bad_get
&) {
717 throw bad_cmd_get(k
, cmdmap
);
722 bool cmd_getval_compat_cephbool(
723 const cmdmap_t
& cmdmap
,
724 const std::string
& k
, bool& val
)
727 return cmd_getval(cmdmap
, k
, val
);
728 } catch (bad_cmd_get
& e
) {
729 // try as legacy/compat CephChoices
731 if (!cmd_getval(cmdmap
, k
, t
)) {
734 std::string expected
= "--"s
+ k
;
735 std::replace(expected
.begin(), expected
.end(), '_', '-');
736 val
= (t
== expected
);