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 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
);
38 *command_args
= {args
.begin(), args
.end()};
40 // Scan command line arguments for ceph global init args (those are
41 // filtered out from args vector by global_init).
43 auto cursor
= args
.begin();
44 for (auto &arg
: cmd_args
) {
46 for (; iter
!= args
.end(); iter
++) {
51 if (iter
== args
.end()) {
52 // filtered out by global_init
53 global_init_args
->push_back(arg
);
62 std::string
format_command_spec(const Shell::CommandSpec
&spec
) {
63 return joinify
<std::string
>(spec
.begin(), spec
.end(), " ");
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();
71 while (spec_it
!= spec
.end() && alias_it
!= alias_spec
.end() &&
72 *spec_it
== *alias_it
) {
77 ceph_assert(spec_it
!= spec
.end() && alias_it
!= alias_spec
.end());
80 return joinify
<std::string
>(alias_spec
.begin(), alias_spec
.end(), " ");
82 return "... " + joinify
<std::string
>(alias_it
, alias_spec
.end(), " ");
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
) + ")";
95 std::string
format_option_suffix(
96 const boost::shared_ptr
<po::option_description
> &option
) {
98 if (option
->semantic()->max_tokens() != 0) {
99 if (option
->description().find("path") != std::string::npos
||
100 option
->description().find("file") != std::string::npos
) {
102 } else if (option
->description().find("host") != std::string::npos
) {
111 } // anonymous namespace
113 std::vector
<Shell::Action
*>& Shell::get_actions() {
114 static std::vector
<Action
*> actions
;
119 std::set
<std::string
>& Shell::get_switch_arguments() {
120 static std::set
<std::string
> switch_arguments
;
122 return switch_arguments
;
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
;
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
);
139 std::vector
<std::string
> command_spec
;
140 get_command_spec(arguments
, &command_spec
);
141 bool is_alias
= true;
143 if (command_spec
.empty() || command_spec
== CommandSpec({"help"})) {
144 // list all available actions
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
);
155 print_action_help(action
, is_alias
);
158 } else if (command_spec
[0] == BASH_COMPLETION_SPEC
) {
159 command_spec
.erase(command_spec
.begin());
160 print_bash_completion(command_spec
);
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
);
171 po::variables_map vm
;
173 po::options_description positional_opts
;
174 po::options_description command_opts
;
175 (*action
->get_arguments
)(&positional_opts
, &command_opts
);
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
> >(), "");
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)
193 positional_options
.add(at::POSITIONAL_ARGUMENTS
.c_str(), max_count
);
196 po::options_description group_opts
;
197 group_opts
.add(command_opts
)
200 po::store(po::command_line_parser(arguments
)
201 .style(po::command_line_style::default_style
&
202 ~po::command_line_style::allow_guessing
)
204 .positional(positional_options
)
207 if (vm
[at::POSITIONAL_COMMAND_SPEC
].as
<std::vector
<std::string
> >() !=
209 std::cerr
<< "rbd: failed to parse command" << std::endl
;
213 int r
= (*action
->execute
)(vm
, ceph_global_init_args
);
216 for (auto opt
: vm
) {
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
);
224 } catch (exception
& e
) {
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
;
237 prefix_style
= po::command_line_style::allow_dash_for_short
;
238 } else if (pos
== std::string::npos
) {
242 for (size_t i
= 0; i
< global_opts
.options().size(); ++i
) {
243 std::string param_name
= global_opts
.options()[i
]->canonical_display_name(
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
);
257 } catch (po::required_option
& e
) {
258 std::cerr
<< "rbd: " << e
.what() << std::endl
;
260 } catch (po::too_many_positional_options_error
& e
) {
261 std::cerr
<< "rbd: too many arguments" << std::endl
;
263 } catch (po::error
& e
) {
264 std::cerr
<< "rbd: " << e
.what() << std::endl
;
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
};
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());
286 } else if (arg
[0] == '-') {
287 // if the option is not a switch, skip its value
288 if (arg
.size() >= 2 &&
290 get_switch_arguments().count(arg
.substr(1, 1)) == 0) &&
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
) {
298 command_spec
->push_back(arg
);
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
;
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
;
337 void Shell::get_global_options(po::options_description
*opts
) {
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");
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
;
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
; });
361 std::cout
<< OptionPrinter::POSITIONAL_ARGUMENTS
<< ":" << std::endl
362 << " <command>" << std::endl
;
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());
373 name_width
+= indent
.size();
374 name_width
= std::min(name_width
, OptionPrinter::MAX_DESCRIPTION_OFFSET
) + 1;
376 for (size_t i
= 0; i
< actions
.size(); ++i
) {
377 Action
*action
= actions
[i
];
378 if (!action
->visible
)
380 std::stringstream ss
;
382 << format_command_name(action
->command_spec
, action
->alias_command_spec
);
384 std::cout
<< ss
.str();
385 if (!action
->description
.empty()) {
386 IndentStream
indent_stream(name_width
, ss
.str().size(),
387 OptionPrinter::LINE_WIDTH
,
389 indent_stream
<< action
->description
<< std::endl
;
391 std::cout
<< std::endl
;
395 po::options_description global_opts
;
396 get_global_options(&global_opts
);
398 std::cout
<< std::endl
<< OptionPrinter::OPTIONAL_ARGUMENTS
<< ":" << std::endl
;
399 OptionPrinter::print_optional(global_opts
, name_width
, std::cout
);
401 std::cout
<< std::endl
402 << "See '" << APP_NAME
<< " help <command>' for help on a specific "
403 << "command." << std::endl
;
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();
412 po::options_description positional
;
413 po::options_description options
;
414 (*action
->get_arguments
)(&positional
, &options
);
416 OptionPrinter
option_printer(positional
, options
);
417 option_printer
.print_short(std::cout
, ss
.str().size());
419 if (!action
->description
.empty()) {
420 std::cout
<< std::endl
<< action
->description
<< std::endl
;
423 std::cout
<< std::endl
;
424 option_printer
.print_detailed(std::cout
);
426 if (!action
->help
.empty()) {
427 std::cout
<< action
->help
<< std::endl
;
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
;
439 void Shell::print_bash_completion(const CommandSpec
&command_spec
) {
441 bool is_alias
= true;
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
);
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
);
454 std::cout
<< "|help";
455 for (size_t i
= 0; i
< get_actions().size(); ++i
) {
456 Action
*action
= get_actions()[i
];
458 << joinify
<std::string
>(action
->command_spec
.begin(),
459 action
->command_spec
.end(), " ");
460 if (!action
->alias_command_spec
.empty()) {
462 << joinify
<std::string
>(action
->alias_command_spec
.begin(),
463 action
->alias_command_spec
.end(),
468 std::cout
<< "|" << std::endl
;
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
));
478 std::cout
<< "|--" << long_name
<< format_option_suffix(option
);
479 if (long_name
!= short_name
) {
480 std::cout
<< "|" << short_name
<< format_option_suffix(option
);