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