]> git.proxmox.com Git - ceph.git/blob - ceph/src/tools/rbd/Shell.cc
import ceph quincy 17.2.4
[ceph.git] / ceph / src / tools / rbd / Shell.cc
1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
3
4 #include "tools/rbd/Shell.h"
5 #include "tools/rbd/ArgumentTypes.h"
6 #include "tools/rbd/IndentStream.h"
7 #include "tools/rbd/OptionPrinter.h"
8 #include "common/ceph_argparse.h"
9 #include "common/config.h"
10 #include "global/global_context.h"
11 #include "global/global_init.h"
12 #include "include/stringify.h"
13 #include <algorithm>
14 #include <iostream>
15 #include <set>
16
17 namespace rbd {
18
19 namespace at = argument_types;
20 namespace po = boost::program_options;
21
22 namespace {
23
24 static const std::string APP_NAME("rbd");
25 static const std::string HELP_SPEC("help");
26 static const std::string BASH_COMPLETION_SPEC("bash-completion");
27
28 boost::intrusive_ptr<CephContext> global_init(
29 int argc, const char **argv, std::vector<std::string> *command_args,
30 std::vector<std::string> *global_init_args) {
31 auto cmd_args = argv_to_vec(argc, argv);
32 std::vector<const char*> args(cmd_args);
33 auto cct = global_init(NULL, args, CEPH_ENTITY_TYPE_CLIENT,
34 CODE_ENVIRONMENT_UTILITY,
35 CINIT_FLAG_NO_MON_CONFIG);
36
37 *command_args = {args.begin(), args.end()};
38
39 // Scan command line arguments for ceph global init args (those are
40 // filtered out from args vector by global_init).
41
42 auto cursor = args.begin();
43 for (auto &arg : cmd_args) {
44 auto iter = cursor;
45 for (; iter != args.end(); iter++) {
46 if (*iter == arg) {
47 break;
48 }
49 }
50 if (iter == args.end()) {
51 // filtered out by global_init
52 global_init_args->push_back(arg);
53 } else {
54 cursor = ++iter;
55 }
56 }
57
58 return cct;
59 }
60
61 std::string format_command_spec(const Shell::CommandSpec &spec) {
62 return joinify<std::string>(spec.begin(), spec.end(), " ");
63 }
64
65 std::string format_alias_spec(const Shell::CommandSpec &spec,
66 const Shell::CommandSpec &alias_spec) {
67 auto spec_it = spec.begin();
68 auto alias_it = alias_spec.begin();
69 int level = 0;
70 while (spec_it != spec.end() && alias_it != alias_spec.end() &&
71 *spec_it == *alias_it) {
72 spec_it++;
73 alias_it++;
74 level++;
75 }
76 ceph_assert(spec_it != spec.end() && alias_it != alias_spec.end());
77
78 if (level < 2) {
79 return joinify<std::string>(alias_spec.begin(), alias_spec.end(), " ");
80 } else {
81 return "... " + joinify<std::string>(alias_it, alias_spec.end(), " ");
82 }
83 }
84
85 std::string format_command_name(const Shell::CommandSpec &spec,
86 const Shell::CommandSpec &alias_spec) {
87 std::string name = format_command_spec(spec);
88 if (!alias_spec.empty()) {
89 name += " (" + format_alias_spec(spec, alias_spec) + ")";
90 }
91 return name;
92 }
93
94 std::string format_option_suffix(
95 const boost::shared_ptr<po::option_description> &option) {
96 std::string suffix;
97 if (option->semantic()->max_tokens() != 0) {
98 if (option->description().find("path") != std::string::npos ||
99 option->description().find("file") != std::string::npos) {
100 suffix += " path";
101 } else if (option->description().find("host") != std::string::npos) {
102 suffix += " host";
103 } else {
104 suffix += " arg";
105 }
106 }
107 return suffix;
108 }
109
110 } // anonymous namespace
111
112 std::vector<Shell::Action *>& Shell::get_actions() {
113 static std::vector<Action *> actions;
114
115 return actions;
116 }
117
118 std::set<std::string>& Shell::get_switch_arguments() {
119 static std::set<std::string> switch_arguments;
120
121 return switch_arguments;
122 }
123
124 void print_deprecated_warning(po::option_description option, std::string description) {
125 auto pos = description.find_first_of(":");
126 if (pos != std::string::npos) {
127 std::string param = description.substr(pos + 1, description.size() - pos - 2);
128 std::cerr << "rbd: " << option.format_name() << " is deprecated, use --"
129 << param << std::endl;
130 }
131 }
132
133 int Shell::execute(int argc, const char **argv) {
134 std::vector<std::string> arguments;
135 std::vector<std::string> ceph_global_init_args;
136 auto cct = global_init(argc, argv, &arguments, &ceph_global_init_args);
137
138 std::vector<std::string> command_spec;
139 get_command_spec(arguments, &command_spec);
140 bool is_alias = true;
141
142 if (command_spec.empty() || command_spec == CommandSpec({"help"})) {
143 // list all available actions
144 print_help();
145 return 0;
146 } else if (command_spec[0] == HELP_SPEC) {
147 // list help for specific action
148 command_spec.erase(command_spec.begin());
149 Action *action = find_action(command_spec, NULL, &is_alias);
150 if (action == NULL) {
151 print_unknown_action(command_spec);
152 return EXIT_FAILURE;
153 } else {
154 print_action_help(action, is_alias);
155 return 0;
156 }
157 } else if (command_spec[0] == BASH_COMPLETION_SPEC) {
158 command_spec.erase(command_spec.begin());
159 print_bash_completion(command_spec);
160 return 0;
161 }
162
163 CommandSpec *matching_spec;
164 Action *action = find_action(command_spec, &matching_spec, &is_alias);
165 if (action == NULL) {
166 print_unknown_action(command_spec);
167 return EXIT_FAILURE;
168 }
169
170 po::variables_map vm;
171 try {
172 po::options_description positional_opts;
173 po::options_description command_opts;
174 (*action->get_arguments)(&positional_opts, &command_opts);
175
176 // dynamically allocate options for our command (e.g. snap list) and
177 // its associated positional arguments
178 po::options_description argument_opts;
179 argument_opts.add_options()
180 (at::POSITIONAL_COMMAND_SPEC.c_str(),
181 po::value<std::vector<std::string> >()->required(), "")
182 (at::POSITIONAL_ARGUMENTS.c_str(),
183 po::value<std::vector<std::string> >(), "");
184
185 po::positional_options_description positional_options;
186 positional_options.add(at::POSITIONAL_COMMAND_SPEC.c_str(),
187 matching_spec->size());
188 if (!positional_opts.options().empty()) {
189 int max_count = positional_opts.options().size();
190 if (positional_opts.options().back()->semantic()->max_tokens() > 1)
191 max_count = -1;
192 positional_options.add(at::POSITIONAL_ARGUMENTS.c_str(), max_count);
193 }
194
195 po::options_description group_opts;
196 group_opts.add(command_opts)
197 .add(argument_opts);
198
199 po::store(po::command_line_parser(arguments)
200 .style(po::command_line_style::default_style &
201 ~po::command_line_style::allow_guessing)
202 .options(group_opts)
203 .positional(positional_options)
204 .run(), vm);
205
206 if (vm[at::POSITIONAL_COMMAND_SPEC].as<std::vector<std::string> >() !=
207 *matching_spec) {
208 std::cerr << "rbd: failed to parse command" << std::endl;
209 return EXIT_FAILURE;
210 }
211
212 int r = (*action->execute)(vm, ceph_global_init_args);
213
214 if (vm.size() > 0) {
215 for (auto opt : vm) {
216 try {
217 auto option = command_opts.find(opt.first, false);
218 auto description = option.description();
219 auto result = boost::find_first(description, "deprecated");
220 if (!result.empty()) {
221 print_deprecated_warning(option, description);
222 }
223 } catch (std::exception& e) {
224 continue;
225 }
226 }
227 }
228
229 po::options_description global_opts;
230 get_global_options(&global_opts);
231 auto it = ceph_global_init_args.begin();
232 for ( ; it != ceph_global_init_args.end(); ++it) {
233 auto pos = (*it).find_last_of("-");
234 auto prefix_style = po::command_line_style::allow_long;
235 if (pos == 0) {
236 prefix_style = po::command_line_style::allow_dash_for_short;
237 } else if (pos == std::string::npos) {
238 continue;
239 }
240
241 for (size_t i = 0; i < global_opts.options().size(); ++i) {
242 std::string param_name = global_opts.options()[i]->canonical_display_name(
243 prefix_style);
244 auto description = global_opts.options()[i]->description();
245 auto result = boost::find_first(description, "deprecated");
246 if (!result.empty() && *it == param_name) {
247 print_deprecated_warning(*global_opts.options()[i], description);
248 break;
249 }
250 }
251 }
252
253 if (r != 0) {
254 return std::abs(r);
255 }
256 } catch (po::required_option& e) {
257 std::cerr << "rbd: " << e.what() << std::endl;
258 return EXIT_FAILURE;
259 } catch (po::too_many_positional_options_error& e) {
260 std::cerr << "rbd: too many arguments" << std::endl;
261 return EXIT_FAILURE;
262 } catch (po::error& e) {
263 std::cerr << "rbd: " << e.what() << std::endl;
264 return EXIT_FAILURE;
265 }
266
267 return 0;
268 }
269
270 void Shell::get_command_spec(const std::vector<std::string> &arguments,
271 std::vector<std::string> *command_spec) {
272 for (size_t i = 0; i < arguments.size(); ++i) {
273 std::string arg(arguments[i]);
274 if (arg == "-h" || arg == "--help") {
275 *command_spec = {HELP_SPEC};
276 return;
277 } else if (arg == "--") {
278 // all arguments after a double-dash are positional
279 if (i + 1 < arguments.size()) {
280 command_spec->insert(command_spec->end(),
281 arguments.data() + i + 1,
282 arguments.data() + arguments.size());
283 }
284 return;
285 } else if (arg[0] == '-') {
286 // if the option is not a switch, skip its value
287 if (arg.size() >= 2 &&
288 (arg[1] == '-' ||
289 get_switch_arguments().count(arg.substr(1, 1)) == 0) &&
290 (arg[1] != '-' ||
291 get_switch_arguments().count(arg.substr(2, std::string::npos)) == 0) &&
292 at::SWITCH_ARGUMENTS.count(arg.substr(2, std::string::npos)) == 0 &&
293 arg.find('=') == std::string::npos) {
294 ++i;
295 }
296 } else {
297 command_spec->push_back(arg);
298 }
299 }
300 }
301
302 Shell::Action *Shell::find_action(const CommandSpec &command_spec,
303 CommandSpec **matching_spec, bool *is_alias) {
304 // sort such that all "trash purge schedule ..." actions come before
305 // "trash purge"
306 std::vector<Action *> actions(get_actions());
307 std::sort(actions.begin(), actions.end(), [](auto lhs, auto rhs) {
308 return lhs->command_spec.size() > rhs->command_spec.size();
309 });
310
311 for (Action *action : actions) {
312 if (action->command_spec.size() <= command_spec.size()) {
313 if (std::equal(action->command_spec.begin(),
314 action->command_spec.end(),
315 command_spec.begin())) {
316 if (matching_spec != NULL) {
317 *matching_spec = &action->command_spec;
318 }
319 *is_alias = false;
320 return action;
321 }
322 }
323 if (!action->alias_command_spec.empty() &&
324 action->alias_command_spec.size() <= command_spec.size()) {
325 if (std::equal(action->alias_command_spec.begin(),
326 action->alias_command_spec.end(),
327 command_spec.begin())) {
328 if (matching_spec != NULL) {
329 *matching_spec = &action->alias_command_spec;
330 }
331 *is_alias = true;
332 return action;
333 }
334 }
335 }
336 return NULL;
337 }
338
339 void Shell::get_global_options(po::options_description *opts) {
340 opts->add_options()
341 ((at::CONFIG_PATH + ",c").c_str(), po::value<std::string>(), "path to cluster configuration")
342 ("cluster", po::value<std::string>(), "cluster name")
343 ("id", po::value<std::string>(), "client id (without 'client.' prefix)")
344 ("user", po::value<std::string>(), "deprecated[:id]")
345 ("name,n", po::value<std::string>(), "client name")
346 ("mon_host,m", po::value<std::string>(), "monitor host")
347 ("secret", po::value<at::Secret>(), "deprecated[:keyfile]")
348 ("keyfile,K", po::value<std::string>(), "path to secret key")
349 ("keyring,k", po::value<std::string>(), "path to keyring");
350 }
351
352 void Shell::print_help() {
353 std::cout << "usage: " << APP_NAME << " <command> ..."
354 << std::endl << std::endl
355 << "Command-line interface for managing Ceph RBD images."
356 << std::endl << std::endl;
357
358 std::vector<Action *> actions(get_actions());
359 std::sort(actions.begin(), actions.end(),
360 [](Action *lhs, Action *rhs) { return lhs->command_spec <
361 rhs->command_spec; });
362
363 std::cout << OptionPrinter::POSITIONAL_ARGUMENTS << ":" << std::endl
364 << " <command>" << std::endl;
365
366 // since the commands have spaces, we have to build our own formatter
367 std::string indent(4, ' ');
368 size_t name_width = OptionPrinter::MIN_NAME_WIDTH;
369 for (size_t i = 0; i < actions.size(); ++i) {
370 Action *action = actions[i];
371 std::string name = format_command_name(action->command_spec,
372 action->alias_command_spec);
373 name_width = std::max(name_width, name.size());
374 }
375 name_width += indent.size();
376 name_width = std::min(name_width, OptionPrinter::MAX_DESCRIPTION_OFFSET) + 1;
377
378 for (size_t i = 0; i < actions.size(); ++i) {
379 Action *action = actions[i];
380 if (!action->visible)
381 continue;
382 std::stringstream ss;
383 ss << indent
384 << format_command_name(action->command_spec, action->alias_command_spec);
385
386 std::cout << ss.str();
387 if (!action->description.empty()) {
388 IndentStream indent_stream(name_width, ss.str().size(),
389 OptionPrinter::LINE_WIDTH,
390 std::cout);
391 indent_stream << action->description << std::endl;
392 } else {
393 std::cout << std::endl;
394 }
395 }
396
397 po::options_description global_opts;
398 get_global_options(&global_opts);
399
400 std::cout << std::endl << OptionPrinter::OPTIONAL_ARGUMENTS << ":" << std::endl;
401 OptionPrinter::print_optional(global_opts, name_width, std::cout);
402
403 std::cout << std::endl
404 << "See '" << APP_NAME << " help <command>' for help on a specific "
405 << "command." << std::endl;
406 }
407
408 void Shell::print_action_help(Action *action, bool is_alias) {
409 std::stringstream ss;
410 ss << "usage: " << APP_NAME << " "
411 << format_command_spec(is_alias ? action->alias_command_spec : action->command_spec);
412 std::cout << ss.str();
413
414 po::options_description positional;
415 po::options_description options;
416 (*action->get_arguments)(&positional, &options);
417
418 OptionPrinter option_printer(positional, options);
419 option_printer.print_short(std::cout, ss.str().size());
420
421 if (!action->description.empty()) {
422 std::cout << std::endl << action->description << std::endl;
423 }
424
425 std::cout << std::endl;
426 option_printer.print_detailed(std::cout);
427
428 if (!action->help.empty()) {
429 std::cout << action->help << std::endl;
430 }
431 }
432
433 void Shell::print_unknown_action(const std::vector<std::string> &command_spec) {
434 std::cerr << "error: unknown option '"
435 << joinify<std::string>(command_spec.begin(),
436 command_spec.end(), " ") << "'"
437 << std::endl << std::endl;
438 print_help();
439 }
440
441 void Shell::print_bash_completion(const CommandSpec &command_spec) {
442
443 bool is_alias = true;
444
445 Action *action = find_action(command_spec, NULL, &is_alias);
446 po::options_description global_opts;
447 get_global_options(&global_opts);
448 print_bash_completion_options(global_opts);
449
450 if (action != nullptr) {
451 po::options_description positional_opts;
452 po::options_description command_opts;
453 (*action->get_arguments)(&positional_opts, &command_opts);
454 print_bash_completion_options(command_opts);
455 } else {
456 std::cout << "|help";
457 for (size_t i = 0; i < get_actions().size(); ++i) {
458 Action *action = get_actions()[i];
459 std::cout << "|"
460 << joinify<std::string>(action->command_spec.begin(),
461 action->command_spec.end(), " ");
462 if (!action->alias_command_spec.empty()) {
463 std::cout << "|"
464 << joinify<std::string>(action->alias_command_spec.begin(),
465 action->alias_command_spec.end(),
466 " ");
467 }
468 }
469 }
470 std::cout << "|" << std::endl;
471 }
472
473 void Shell::print_bash_completion_options(const po::options_description &ops) {
474 for (size_t i = 0; i < ops.options().size(); ++i) {
475 auto option = ops.options()[i];
476 std::string long_name(option->canonical_display_name(0));
477 std::string short_name(option->canonical_display_name(
478 po::command_line_style::allow_dash_for_short));
479
480 std::cout << "|--" << long_name << format_option_suffix(option);
481 if (long_name != short_name) {
482 std::cout << "|" << short_name << format_option_suffix(option);
483 }
484 }
485 }
486
487 } // namespace rbd