1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
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"
19 namespace at
= argument_types
;
20 namespace po
= boost::program_options
;
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");
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
);
37 *command_args
= {args
.begin(), args
.end()};
39 // Scan command line arguments for ceph global init args (those are
40 // filtered out from args vector by global_init).
42 auto cursor
= args
.begin();
43 for (auto &arg
: cmd_args
) {
45 for (; iter
!= args
.end(); iter
++) {
50 if (iter
== args
.end()) {
51 // filtered out by global_init
52 global_init_args
->push_back(arg
);
61 std::string
format_command_spec(const Shell::CommandSpec
&spec
) {
62 return joinify
<std::string
>(spec
.begin(), spec
.end(), " ");
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();
70 while (spec_it
!= spec
.end() && alias_it
!= alias_spec
.end() &&
71 *spec_it
== *alias_it
) {
76 ceph_assert(spec_it
!= spec
.end() && alias_it
!= alias_spec
.end());
79 return joinify
<std::string
>(alias_spec
.begin(), alias_spec
.end(), " ");
81 return "... " + joinify
<std::string
>(alias_it
, alias_spec
.end(), " ");
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
) + ")";
94 std::string
format_option_suffix(
95 const boost::shared_ptr
<po::option_description
> &option
) {
97 if (option
->semantic()->max_tokens() != 0) {
98 if (option
->description().find("path") != std::string::npos
||
99 option
->description().find("file") != std::string::npos
) {
101 } else if (option
->description().find("host") != std::string::npos
) {
110 } // anonymous namespace
112 std::vector
<Shell::Action
*>& Shell::get_actions() {
113 static std::vector
<Action
*> actions
;
118 std::set
<std::string
>& Shell::get_switch_arguments() {
119 static std::set
<std::string
> switch_arguments
;
121 return switch_arguments
;
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
;
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
);
138 std::vector
<std::string
> command_spec
;
139 get_command_spec(arguments
, &command_spec
);
140 bool is_alias
= true;
142 if (command_spec
.empty() || command_spec
== CommandSpec({"help"})) {
143 // list all available actions
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
);
154 print_action_help(action
, is_alias
);
157 } else if (command_spec
[0] == BASH_COMPLETION_SPEC
) {
158 command_spec
.erase(command_spec
.begin());
159 print_bash_completion(command_spec
);
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
);
170 po::variables_map vm
;
172 po::options_description positional_opts
;
173 po::options_description command_opts
;
174 (*action
->get_arguments
)(&positional_opts
, &command_opts
);
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
> >(), "");
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)
192 positional_options
.add(at::POSITIONAL_ARGUMENTS
.c_str(), max_count
);
195 po::options_description group_opts
;
196 group_opts
.add(command_opts
)
199 po::store(po::command_line_parser(arguments
)
200 .style(po::command_line_style::default_style
&
201 ~po::command_line_style::allow_guessing
)
203 .positional(positional_options
)
206 if (vm
[at::POSITIONAL_COMMAND_SPEC
].as
<std::vector
<std::string
> >() !=
208 std::cerr
<< "rbd: failed to parse command" << std::endl
;
212 int r
= (*action
->execute
)(vm
, ceph_global_init_args
);
215 for (auto opt
: vm
) {
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
);
223 } catch (std::exception
& e
) {
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
;
236 prefix_style
= po::command_line_style::allow_dash_for_short
;
237 } else if (pos
== std::string::npos
) {
241 for (size_t i
= 0; i
< global_opts
.options().size(); ++i
) {
242 std::string param_name
= global_opts
.options()[i
]->canonical_display_name(
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
);
256 } catch (po::required_option
& e
) {
257 std::cerr
<< "rbd: " << e
.what() << std::endl
;
259 } catch (po::too_many_positional_options_error
& e
) {
260 std::cerr
<< "rbd: too many arguments" << std::endl
;
262 } catch (po::error
& e
) {
263 std::cerr
<< "rbd: " << e
.what() << std::endl
;
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
};
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());
285 } else if (arg
[0] == '-') {
286 // if the option is not a switch, skip its value
287 if (arg
.size() >= 2 &&
289 get_switch_arguments().count(arg
.substr(1, 1)) == 0) &&
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
) {
297 command_spec
->push_back(arg
);
302 Shell::Action
*Shell::find_action(const CommandSpec
&command_spec
,
303 CommandSpec
**matching_spec
, bool *is_alias
) {
304 for (size_t i
= 0; i
< get_actions().size(); ++i
) {
305 Action
*action
= get_actions()[i
];
306 if (action
->command_spec
.size() <= command_spec
.size()) {
307 if (std::includes(action
->command_spec
.begin(),
308 action
->command_spec
.end(),
309 command_spec
.begin(),
310 command_spec
.begin() + action
->command_spec
.size())) {
311 if (matching_spec
!= NULL
) {
312 *matching_spec
= &action
->command_spec
;
318 if (!action
->alias_command_spec
.empty() &&
319 action
->alias_command_spec
.size() <= command_spec
.size()) {
320 if (std::includes(action
->alias_command_spec
.begin(),
321 action
->alias_command_spec
.end(),
322 command_spec
.begin(),
323 command_spec
.begin() +
324 action
->alias_command_spec
.size())) {
325 if (matching_spec
!= NULL
) {
326 *matching_spec
= &action
->alias_command_spec
;
336 void Shell::get_global_options(po::options_description
*opts
) {
338 ((at::CONFIG_PATH
+ ",c").c_str(), po::value
<std::string
>(), "path to cluster configuration")
339 ("cluster", po::value
<std::string
>(), "cluster name")
340 ("id", po::value
<std::string
>(), "client id (without 'client.' prefix)")
341 ("user", po::value
<std::string
>(), "deprecated[:id]")
342 ("name,n", po::value
<std::string
>(), "client name")
343 ("mon_host,m", po::value
<std::string
>(), "monitor host")
344 ("secret", po::value
<at::Secret
>(), "deprecated[:keyfile]")
345 ("keyfile,K", po::value
<std::string
>(), "path to secret key")
346 ("keyring,k", po::value
<std::string
>(), "path to keyring");
349 void Shell::print_help() {
350 std::cout
<< "usage: " << APP_NAME
<< " <command> ..."
351 << std::endl
<< std::endl
352 << "Command-line interface for managing Ceph RBD images."
353 << std::endl
<< std::endl
;
355 std::vector
<Action
*> actions(get_actions());
356 std::sort(actions
.begin(), actions
.end(),
357 [](Action
*lhs
, Action
*rhs
) { return lhs
->command_spec
<
358 rhs
->command_spec
; });
360 std::cout
<< OptionPrinter::POSITIONAL_ARGUMENTS
<< ":" << std::endl
361 << " <command>" << std::endl
;
363 // since the commands have spaces, we have to build our own formatter
364 std::string
indent(4, ' ');
365 size_t name_width
= OptionPrinter::MIN_NAME_WIDTH
;
366 for (size_t i
= 0; i
< actions
.size(); ++i
) {
367 Action
*action
= actions
[i
];
368 std::string name
= format_command_name(action
->command_spec
,
369 action
->alias_command_spec
);
370 name_width
= std::max(name_width
, name
.size());
372 name_width
+= indent
.size();
373 name_width
= std::min(name_width
, OptionPrinter::MAX_DESCRIPTION_OFFSET
) + 1;
375 for (size_t i
= 0; i
< actions
.size(); ++i
) {
376 Action
*action
= actions
[i
];
377 if (!action
->visible
)
379 std::stringstream ss
;
381 << format_command_name(action
->command_spec
, action
->alias_command_spec
);
383 std::cout
<< ss
.str();
384 if (!action
->description
.empty()) {
385 IndentStream
indent_stream(name_width
, ss
.str().size(),
386 OptionPrinter::LINE_WIDTH
,
388 indent_stream
<< action
->description
<< std::endl
;
390 std::cout
<< std::endl
;
394 po::options_description global_opts
;
395 get_global_options(&global_opts
);
397 std::cout
<< std::endl
<< OptionPrinter::OPTIONAL_ARGUMENTS
<< ":" << std::endl
;
398 OptionPrinter::print_optional(global_opts
, name_width
, std::cout
);
400 std::cout
<< std::endl
401 << "See '" << APP_NAME
<< " help <command>' for help on a specific "
402 << "command." << std::endl
;
405 void Shell::print_action_help(Action
*action
, bool is_alias
) {
406 std::stringstream ss
;
407 ss
<< "usage: " << APP_NAME
<< " "
408 << format_command_spec(is_alias
? action
->alias_command_spec
: action
->command_spec
);
409 std::cout
<< ss
.str();
411 po::options_description positional
;
412 po::options_description options
;
413 (*action
->get_arguments
)(&positional
, &options
);
415 OptionPrinter
option_printer(positional
, options
);
416 option_printer
.print_short(std::cout
, ss
.str().size());
418 if (!action
->description
.empty()) {
419 std::cout
<< std::endl
<< action
->description
<< std::endl
;
422 std::cout
<< std::endl
;
423 option_printer
.print_detailed(std::cout
);
425 if (!action
->help
.empty()) {
426 std::cout
<< action
->help
<< std::endl
;
430 void Shell::print_unknown_action(const std::vector
<std::string
> &command_spec
) {
431 std::cerr
<< "error: unknown option '"
432 << joinify
<std::string
>(command_spec
.begin(),
433 command_spec
.end(), " ") << "'"
434 << std::endl
<< std::endl
;
438 void Shell::print_bash_completion(const CommandSpec
&command_spec
) {
440 bool is_alias
= true;
442 Action
*action
= find_action(command_spec
, NULL
, &is_alias
);
443 po::options_description global_opts
;
444 get_global_options(&global_opts
);
445 print_bash_completion_options(global_opts
);
447 if (action
!= nullptr) {
448 po::options_description positional_opts
;
449 po::options_description command_opts
;
450 (*action
->get_arguments
)(&positional_opts
, &command_opts
);
451 print_bash_completion_options(command_opts
);
453 std::cout
<< "|help";
454 for (size_t i
= 0; i
< get_actions().size(); ++i
) {
455 Action
*action
= get_actions()[i
];
457 << joinify
<std::string
>(action
->command_spec
.begin(),
458 action
->command_spec
.end(), " ");
459 if (!action
->alias_command_spec
.empty()) {
461 << joinify
<std::string
>(action
->alias_command_spec
.begin(),
462 action
->alias_command_spec
.end(),
467 std::cout
<< "|" << std::endl
;
470 void Shell::print_bash_completion_options(const po::options_description
&ops
) {
471 for (size_t i
= 0; i
< ops
.options().size(); ++i
) {
472 auto option
= ops
.options()[i
];
473 std::string
long_name(option
->canonical_display_name(0));
474 std::string
short_name(option
->canonical_display_name(
475 po::command_line_style::allow_dash_for_short
));
477 std::cout
<< "|--" << long_name
<< format_option_suffix(option
);
478 if (long_name
!= short_name
) {
479 std::cout
<< "|" << short_name
<< format_option_suffix(option
);