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/config.h"
9 #include "global/global_context.h"
10 #include "include/stringify.h"
17 namespace at = argument_types;
18 namespace po = boost::program_options;
22 static const std::string APP_NAME("rbd");
23 static const std::string HELP_SPEC("help");
24 static const std::string BASH_COMPLETION_SPEC("bash-completion");
26 std::string format_command_spec(const Shell::CommandSpec &spec) {
27 return joinify<std::string>(spec.begin(), spec.end(), " ");
30 std::string format_command_name(const Shell::CommandSpec &spec,
31 const Shell::CommandSpec &alias_spec) {
32 std::string name = format_command_spec(spec);
33 if (!alias_spec.empty()) {
34 name += " (" + format_command_spec(alias_spec) + ")";
39 std::string format_option_suffix(
40 const boost::shared_ptr<po::option_description> &option) {
42 if (option->semantic()->max_tokens() != 0) {
43 if (option->description().find("path") != std::string::npos ||
44 option->description().find("file") != std::string::npos) {
46 } else if (option->description().find("host") != std::string::npos) {
55 } // anonymous namespace
57 std::vector<Shell::Action *>& Shell::get_actions() {
58 static std::vector<Action *> actions;
63 std::set<std::string>& Shell::get_switch_arguments() {
64 static std::set<std::string> switch_arguments;
66 return switch_arguments;
69 int Shell::execute(const Arguments& cmdline_arguments) {
71 std::vector<std::string> arguments(cmdline_arguments.begin(),
72 cmdline_arguments.end());
73 std::vector<std::string> command_spec;
74 get_command_spec(arguments, &command_spec);
77 if (command_spec.empty() || command_spec == CommandSpec({"help"})) {
78 // list all available actions
81 } else if (command_spec[0] == HELP_SPEC) {
82 // list help for specific action
83 command_spec.erase(command_spec.begin());
84 Action *action = find_action(command_spec, NULL, &is_alias);
86 print_unknown_action(command_spec);
89 print_action_help(action, is_alias);
92 } else if (command_spec[0] == BASH_COMPLETION_SPEC) {
93 command_spec.erase(command_spec.begin());
94 print_bash_completion(command_spec);
98 CommandSpec *matching_spec;
99 Action *action = find_action(command_spec, &matching_spec, &is_alias);
100 if (action == NULL) {
101 print_unknown_action(command_spec);
105 po::variables_map vm;
107 po::options_description positional_opts;
108 po::options_description command_opts;
109 (*action->get_arguments)(&positional_opts, &command_opts);
111 // dynamically allocate options for our command (e.g. snap list) and
112 // its associated positional arguments
113 po::options_description argument_opts;
114 argument_opts.add_options()
115 (at::POSITIONAL_COMMAND_SPEC.c_str(),
116 po::value<std::vector<std::string> >()->required(), "")
117 (at::POSITIONAL_ARGUMENTS.c_str(),
118 po::value<std::vector<std::string> >(), "");
120 po::positional_options_description positional_options;
121 positional_options.add(at::POSITIONAL_COMMAND_SPEC.c_str(),
122 matching_spec->size());
123 if (!positional_opts.options().empty()) {
124 int max_count = positional_opts.options().size();
125 if (positional_opts.options().back()->semantic()->max_tokens() > 1)
127 positional_options.add(at::POSITIONAL_ARGUMENTS.c_str(), max_count);
130 po::options_description global_opts;
131 get_global_options(&global_opts);
133 po::options_description group_opts;
134 group_opts.add(command_opts)
138 po::store(po::command_line_parser(arguments)
139 .style(po::command_line_style::default_style &
140 ~po::command_line_style::allow_guessing)
142 .positional(positional_options)
145 if (vm[at::POSITIONAL_COMMAND_SPEC].as<std::vector<std::string> >() !=
147 std::cerr << "rbd: failed to parse command" << std::endl;
151 int r = (*action->execute)(vm);
155 } catch (po::required_option& e) {
156 std::cerr << "rbd: " << e.what() << std::endl;
158 } catch (po::too_many_positional_options_error& e) {
159 std::cerr << "rbd: too many arguments" << std::endl;
161 } catch (po::error& e) {
162 std::cerr << "rbd: " << e.what() << std::endl;
169 void Shell::get_command_spec(const std::vector<std::string> &arguments,
170 std::vector<std::string> *command_spec) {
171 for (size_t i = 0; i < arguments.size(); ++i) {
172 std::string arg(arguments[i]);
173 if (arg == "-h" || arg == "--help") {
174 *command_spec = {HELP_SPEC};
176 } else if (arg == "--") {
177 // all arguments after a double-dash are positional
178 if (i + 1 < arguments.size()) {
179 command_spec->insert(command_spec->end(),
180 arguments.data() + i + 1,
181 arguments.data() + arguments.size());
184 } else if (arg[0] == '-') {
185 // if the option is not a switch, skip its value
186 if (arg.size() >= 2 &&
188 get_switch_arguments().count(arg.substr(1, 1)) == 0) &&
190 get_switch_arguments().count(arg.substr(2, std::string::npos)) == 0) &&
191 at::SWITCH_ARGUMENTS.count(arg.substr(2, std::string::npos)) == 0 &&
192 arg.find('=') == std::string::npos) {
196 command_spec->push_back(arg);
201 Shell::Action *Shell::find_action(const CommandSpec &command_spec,
202 CommandSpec **matching_spec, bool *is_alias) {
203 for (size_t i = 0; i < get_actions().size(); ++i) {
204 Action *action = get_actions()[i];
205 if (action->command_spec.size() <= command_spec.size()) {
206 if (std::includes(action->command_spec.begin(),
207 action->command_spec.end(),
208 command_spec.begin(),
209 command_spec.begin() + action->command_spec.size())) {
210 if (matching_spec != NULL) {
211 *matching_spec = &action->command_spec;
217 if (!action->alias_command_spec.empty() &&
218 action->alias_command_spec.size() <= command_spec.size()) {
219 if (std::includes(action->alias_command_spec.begin(),
220 action->alias_command_spec.end(),
221 command_spec.begin(),
222 command_spec.begin() +
223 action->alias_command_spec.size())) {
224 if (matching_spec != NULL) {
225 *matching_spec = &action->alias_command_spec;
235 void Shell::get_global_options(po::options_description *opts) {
237 ((at::CONFIG_PATH + ",c").c_str(), po::value<std::string>(), "path to cluster configuration")
238 ("cluster", po::value<std::string>(), "cluster name")
239 ("id", po::value<std::string>(), "client id (without 'client.' prefix)")
240 ("user", po::value<std::string>(), "client id (without 'client.' prefix)")
241 ("name,n", po::value<std::string>(), "client name")
242 ("mon_host,m", po::value<std::string>(), "monitor host")
243 ("secret", po::value<at::Secret>(), "path to secret key (deprecated)")
244 ("keyfile,K", po::value<std::string>(), "path to secret key")
245 ("keyring,k", po::value<std::string>(), "path to keyring");
248 void Shell::print_help() {
249 std::cout << "usage: " << APP_NAME << " <command> ..."
250 << std::endl << std::endl
251 << "Command-line interface for managing Ceph RBD images."
252 << std::endl << std::endl;
254 std::vector<Action *> actions(get_actions());
255 std::sort(actions.begin(), actions.end(),
256 [](Action *lhs, Action *rhs) { return lhs->command_spec <
257 rhs->command_spec; });
259 std::cout << OptionPrinter::POSITIONAL_ARGUMENTS << ":" << std::endl
260 << " <command>" << std::endl;
262 // since the commands have spaces, we have to build our own formatter
263 std::string indent(4, ' ');
264 size_t name_width = OptionPrinter::MIN_NAME_WIDTH;
265 for (size_t i = 0; i < actions.size(); ++i) {
266 Action *action = actions[i];
267 std::string name = format_command_name(action->command_spec,
268 action->alias_command_spec);
269 name_width = std::max(name_width, name.size());
271 name_width += indent.size();
272 name_width = std::min(name_width, OptionPrinter::MAX_DESCRIPTION_OFFSET) + 1;
274 for (size_t i = 0; i < actions.size(); ++i) {
275 Action *action = actions[i];
276 if (!action->visible)
278 std::stringstream ss;
280 << format_command_name(action->command_spec, action->alias_command_spec);
282 std::cout << ss.str();
283 if (!action->description.empty()) {
284 IndentStream indent_stream(name_width, ss.str().size(),
285 OptionPrinter::LINE_WIDTH,
287 indent_stream << action->description << std::endl;
289 std::cout << std::endl;
293 po::options_description global_opts(OptionPrinter::OPTIONAL_ARGUMENTS);
294 get_global_options(&global_opts);
295 std::cout << std::endl << global_opts << std::endl
296 << "See '" << APP_NAME << " help <command>' for help on a specific "
297 << "command." << std::endl;
300 void Shell::print_action_help(Action *action, bool is_alias) {
301 std::stringstream ss;
302 ss << "usage: " << APP_NAME << " "
303 << format_command_spec(is_alias ? action->alias_command_spec : action->command_spec);
304 std::cout << ss.str();
306 po::options_description positional;
307 po::options_description options;
308 (*action->get_arguments)(&positional, &options);
310 OptionPrinter option_printer(positional, options);
311 option_printer.print_short(std::cout, ss.str().size());
313 if (!action->description.empty()) {
314 std::cout << std::endl << action->description << std::endl;
317 std::cout << std::endl;
318 option_printer.print_detailed(std::cout);
320 if (!action->help.empty()) {
321 std::cout << action->help << std::endl;
325 void Shell::print_unknown_action(const std::vector<std::string> &command_spec) {
326 std::cerr << "error: unknown option '"
327 << joinify<std::string>(command_spec.begin(),
328 command_spec.end(), " ") << "'"
329 << std::endl << std::endl;
333 void Shell::print_bash_completion(const CommandSpec &command_spec) {
335 bool is_alias = true;
337 Action *action = find_action(command_spec, NULL, &is_alias);
338 po::options_description global_opts;
339 get_global_options(&global_opts);
340 print_bash_completion_options(global_opts);
342 if (action != nullptr) {
343 po::options_description positional_opts;
344 po::options_description command_opts;
345 (*action->get_arguments)(&positional_opts, &command_opts);
346 print_bash_completion_options(command_opts);
348 std::cout << "|help";
349 for (size_t i = 0; i < get_actions().size(); ++i) {
350 Action *action = get_actions()[i];
352 << joinify<std::string>(action->command_spec.begin(),
353 action->command_spec.end(), " ");
354 if (!action->alias_command_spec.empty()) {
356 << joinify<std::string>(action->alias_command_spec.begin(),
357 action->alias_command_spec.end(),
362 std::cout << "|" << std::endl;
365 void Shell::print_bash_completion_options(const po::options_description &ops) {
366 for (size_t i = 0; i < ops.options().size(); ++i) {
367 auto option = ops.options()[i];
368 std::string long_name(option->canonical_display_name(0));
369 std::string short_name(option->canonical_display_name(
370 po::command_line_style::allow_dash_for_short));
372 std::cout << "|--" << long_name << format_option_suffix(option);
373 if (long_name != short_name) {
374 std::cout << "|" << short_name << format_option_suffix(option);