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