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 int Shell::execute(int argc
, const char **argv
) {
126 std::vector
<std::string
> arguments
;
127 std::vector
<std::string
> ceph_global_init_args
;
128 auto cct
= global_init(argc
, argv
, &arguments
, &ceph_global_init_args
);
130 std::vector
<std::string
> command_spec
;
131 get_command_spec(arguments
, &command_spec
);
132 bool is_alias
= true;
134 if (command_spec
.empty() || command_spec
== CommandSpec({"help"})) {
135 // list all available actions
138 } else if (command_spec
[0] == HELP_SPEC
) {
139 // list help for specific action
140 command_spec
.erase(command_spec
.begin());
141 Action
*action
= find_action(command_spec
, NULL
, &is_alias
);
142 if (action
== NULL
) {
143 print_unknown_action(command_spec
);
146 print_action_help(action
, is_alias
);
149 } else if (command_spec
[0] == BASH_COMPLETION_SPEC
) {
150 command_spec
.erase(command_spec
.begin());
151 print_bash_completion(command_spec
);
155 CommandSpec
*matching_spec
;
156 Action
*action
= find_action(command_spec
, &matching_spec
, &is_alias
);
157 if (action
== NULL
) {
158 print_unknown_action(command_spec
);
162 po::variables_map vm
;
164 po::options_description positional_opts
;
165 po::options_description command_opts
;
166 (*action
->get_arguments
)(&positional_opts
, &command_opts
);
168 // dynamically allocate options for our command (e.g. snap list) and
169 // its associated positional arguments
170 po::options_description argument_opts
;
171 argument_opts
.add_options()
172 (at::POSITIONAL_COMMAND_SPEC
.c_str(),
173 po::value
<std::vector
<std::string
> >()->required(), "")
174 (at::POSITIONAL_ARGUMENTS
.c_str(),
175 po::value
<std::vector
<std::string
> >(), "");
177 po::positional_options_description positional_options
;
178 positional_options
.add(at::POSITIONAL_COMMAND_SPEC
.c_str(),
179 matching_spec
->size());
180 if (!positional_opts
.options().empty()) {
181 int max_count
= positional_opts
.options().size();
182 if (positional_opts
.options().back()->semantic()->max_tokens() > 1)
184 positional_options
.add(at::POSITIONAL_ARGUMENTS
.c_str(), max_count
);
187 po::options_description group_opts
;
188 group_opts
.add(command_opts
)
191 po::store(po::command_line_parser(arguments
)
192 .style(po::command_line_style::default_style
&
193 ~po::command_line_style::allow_guessing
)
195 .positional(positional_options
)
198 if (vm
[at::POSITIONAL_COMMAND_SPEC
].as
<std::vector
<std::string
> >() !=
200 std::cerr
<< "rbd: failed to parse command" << std::endl
;
204 int r
= (*action
->execute
)(vm
, ceph_global_init_args
);
208 } catch (po::required_option
& e
) {
209 std::cerr
<< "rbd: " << e
.what() << std::endl
;
211 } catch (po::too_many_positional_options_error
& e
) {
212 std::cerr
<< "rbd: too many arguments" << std::endl
;
214 } catch (po::error
& e
) {
215 std::cerr
<< "rbd: " << e
.what() << std::endl
;
222 void Shell::get_command_spec(const std::vector
<std::string
> &arguments
,
223 std::vector
<std::string
> *command_spec
) {
224 for (size_t i
= 0; i
< arguments
.size(); ++i
) {
225 std::string
arg(arguments
[i
]);
226 if (arg
== "-h" || arg
== "--help") {
227 *command_spec
= {HELP_SPEC
};
229 } else if (arg
== "--") {
230 // all arguments after a double-dash are positional
231 if (i
+ 1 < arguments
.size()) {
232 command_spec
->insert(command_spec
->end(),
233 arguments
.data() + i
+ 1,
234 arguments
.data() + arguments
.size());
237 } else if (arg
[0] == '-') {
238 // if the option is not a switch, skip its value
239 if (arg
.size() >= 2 &&
241 get_switch_arguments().count(arg
.substr(1, 1)) == 0) &&
243 get_switch_arguments().count(arg
.substr(2, std::string::npos
)) == 0) &&
244 at::SWITCH_ARGUMENTS
.count(arg
.substr(2, std::string::npos
)) == 0 &&
245 arg
.find('=') == std::string::npos
) {
249 command_spec
->push_back(arg
);
254 Shell::Action
*Shell::find_action(const CommandSpec
&command_spec
,
255 CommandSpec
**matching_spec
, bool *is_alias
) {
256 for (size_t i
= 0; i
< get_actions().size(); ++i
) {
257 Action
*action
= get_actions()[i
];
258 if (action
->command_spec
.size() <= command_spec
.size()) {
259 if (std::includes(action
->command_spec
.begin(),
260 action
->command_spec
.end(),
261 command_spec
.begin(),
262 command_spec
.begin() + action
->command_spec
.size())) {
263 if (matching_spec
!= NULL
) {
264 *matching_spec
= &action
->command_spec
;
270 if (!action
->alias_command_spec
.empty() &&
271 action
->alias_command_spec
.size() <= command_spec
.size()) {
272 if (std::includes(action
->alias_command_spec
.begin(),
273 action
->alias_command_spec
.end(),
274 command_spec
.begin(),
275 command_spec
.begin() +
276 action
->alias_command_spec
.size())) {
277 if (matching_spec
!= NULL
) {
278 *matching_spec
= &action
->alias_command_spec
;
288 void Shell::get_global_options(po::options_description
*opts
) {
290 ((at::CONFIG_PATH
+ ",c").c_str(), po::value
<std::string
>(), "path to cluster configuration")
291 ("cluster", po::value
<std::string
>(), "cluster name")
292 ("id", po::value
<std::string
>(), "client id (without 'client.' prefix)")
293 ("user", po::value
<std::string
>(), "client id (without 'client.' prefix)")
294 ("name,n", po::value
<std::string
>(), "client name")
295 ("mon_host,m", po::value
<std::string
>(), "monitor host")
296 ("secret", po::value
<at::Secret
>(), "path to secret key (deprecated)")
297 ("keyfile,K", po::value
<std::string
>(), "path to secret key")
298 ("keyring,k", po::value
<std::string
>(), "path to keyring");
301 void Shell::print_help() {
302 std::cout
<< "usage: " << APP_NAME
<< " <command> ..."
303 << std::endl
<< std::endl
304 << "Command-line interface for managing Ceph RBD images."
305 << std::endl
<< std::endl
;
307 std::vector
<Action
*> actions(get_actions());
308 std::sort(actions
.begin(), actions
.end(),
309 [](Action
*lhs
, Action
*rhs
) { return lhs
->command_spec
<
310 rhs
->command_spec
; });
312 std::cout
<< OptionPrinter::POSITIONAL_ARGUMENTS
<< ":" << std::endl
313 << " <command>" << std::endl
;
315 // since the commands have spaces, we have to build our own formatter
316 std::string
indent(4, ' ');
317 size_t name_width
= OptionPrinter::MIN_NAME_WIDTH
;
318 for (size_t i
= 0; i
< actions
.size(); ++i
) {
319 Action
*action
= actions
[i
];
320 std::string name
= format_command_name(action
->command_spec
,
321 action
->alias_command_spec
);
322 name_width
= std::max(name_width
, name
.size());
324 name_width
+= indent
.size();
325 name_width
= std::min(name_width
, OptionPrinter::MAX_DESCRIPTION_OFFSET
) + 1;
327 for (size_t i
= 0; i
< actions
.size(); ++i
) {
328 Action
*action
= actions
[i
];
329 if (!action
->visible
)
331 std::stringstream ss
;
333 << format_command_name(action
->command_spec
, action
->alias_command_spec
);
335 std::cout
<< ss
.str();
336 if (!action
->description
.empty()) {
337 IndentStream
indent_stream(name_width
, ss
.str().size(),
338 OptionPrinter::LINE_WIDTH
,
340 indent_stream
<< action
->description
<< std::endl
;
342 std::cout
<< std::endl
;
346 po::options_description
global_opts(OptionPrinter::OPTIONAL_ARGUMENTS
);
347 get_global_options(&global_opts
);
348 std::cout
<< std::endl
<< global_opts
<< std::endl
349 << "See '" << APP_NAME
<< " help <command>' for help on a specific "
350 << "command." << std::endl
;
353 void Shell::print_action_help(Action
*action
, bool is_alias
) {
354 std::stringstream ss
;
355 ss
<< "usage: " << APP_NAME
<< " "
356 << format_command_spec(is_alias
? action
->alias_command_spec
: action
->command_spec
);
357 std::cout
<< ss
.str();
359 po::options_description positional
;
360 po::options_description options
;
361 (*action
->get_arguments
)(&positional
, &options
);
363 OptionPrinter
option_printer(positional
, options
);
364 option_printer
.print_short(std::cout
, ss
.str().size());
366 if (!action
->description
.empty()) {
367 std::cout
<< std::endl
<< action
->description
<< std::endl
;
370 std::cout
<< std::endl
;
371 option_printer
.print_detailed(std::cout
);
373 if (!action
->help
.empty()) {
374 std::cout
<< action
->help
<< std::endl
;
378 void Shell::print_unknown_action(const std::vector
<std::string
> &command_spec
) {
379 std::cerr
<< "error: unknown option '"
380 << joinify
<std::string
>(command_spec
.begin(),
381 command_spec
.end(), " ") << "'"
382 << std::endl
<< std::endl
;
386 void Shell::print_bash_completion(const CommandSpec
&command_spec
) {
388 bool is_alias
= true;
390 Action
*action
= find_action(command_spec
, NULL
, &is_alias
);
391 po::options_description global_opts
;
392 get_global_options(&global_opts
);
393 print_bash_completion_options(global_opts
);
395 if (action
!= nullptr) {
396 po::options_description positional_opts
;
397 po::options_description command_opts
;
398 (*action
->get_arguments
)(&positional_opts
, &command_opts
);
399 print_bash_completion_options(command_opts
);
401 std::cout
<< "|help";
402 for (size_t i
= 0; i
< get_actions().size(); ++i
) {
403 Action
*action
= get_actions()[i
];
405 << joinify
<std::string
>(action
->command_spec
.begin(),
406 action
->command_spec
.end(), " ");
407 if (!action
->alias_command_spec
.empty()) {
409 << joinify
<std::string
>(action
->alias_command_spec
.begin(),
410 action
->alias_command_spec
.end(),
415 std::cout
<< "|" << std::endl
;
418 void Shell::print_bash_completion_options(const po::options_description
&ops
) {
419 for (size_t i
= 0; i
< ops
.options().size(); ++i
) {
420 auto option
= ops
.options()[i
];
421 std::string
long_name(option
->canonical_display_name(0));
422 std::string
short_name(option
->canonical_display_name(
423 po::command_line_style::allow_dash_for_short
));
425 std::cout
<< "|--" << long_name
<< format_option_suffix(option
);
426 if (long_name
!= short_name
) {
427 std::cout
<< "|" << short_name
<< format_option_suffix(option
);