]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
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" | |
11fdf7f2 | 8 | #include "common/ceph_argparse.h" |
7c673cae FG |
9 | #include "common/config.h" |
10 | #include "global/global_context.h" | |
11fdf7f2 | 11 | #include "global/global_init.h" |
7c673cae FG |
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 | ||
11fdf7f2 TL |
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 | ||
7c673cae FG |
62 | std::string format_command_spec(const Shell::CommandSpec &spec) { |
63 | return joinify<std::string>(spec.begin(), spec.end(), " "); | |
64 | } | |
65 | ||
11fdf7f2 TL |
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 | ||
7c673cae FG |
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()) { | |
11fdf7f2 | 90 | name += " (" + format_alias_spec(spec, alias_spec) + ")"; |
7c673cae FG |
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 | ||
11fdf7f2 TL |
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); | |
7c673cae | 129 | |
7c673cae FG |
130 | std::vector<std::string> command_spec; |
131 | get_command_spec(arguments, &command_spec); | |
132 | bool is_alias = true; | |
133 | ||
134 | if (command_spec.empty() || command_spec == CommandSpec({"help"})) { | |
135 | // list all available actions | |
136 | print_help(); | |
137 | return 0; | |
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); | |
144 | return EXIT_FAILURE; | |
145 | } else { | |
146 | print_action_help(action, is_alias); | |
147 | return 0; | |
148 | } | |
149 | } else if (command_spec[0] == BASH_COMPLETION_SPEC) { | |
150 | command_spec.erase(command_spec.begin()); | |
151 | print_bash_completion(command_spec); | |
152 | return 0; | |
153 | } | |
154 | ||
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); | |
159 | return EXIT_FAILURE; | |
160 | } | |
161 | ||
162 | po::variables_map vm; | |
163 | try { | |
164 | po::options_description positional_opts; | |
165 | po::options_description command_opts; | |
166 | (*action->get_arguments)(&positional_opts, &command_opts); | |
167 | ||
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> >(), ""); | |
176 | ||
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) | |
183 | max_count = -1; | |
184 | positional_options.add(at::POSITIONAL_ARGUMENTS.c_str(), max_count); | |
185 | } | |
186 | ||
7c673cae FG |
187 | po::options_description group_opts; |
188 | group_opts.add(command_opts) | |
11fdf7f2 | 189 | .add(argument_opts); |
7c673cae FG |
190 | |
191 | po::store(po::command_line_parser(arguments) | |
192 | .style(po::command_line_style::default_style & | |
193 | ~po::command_line_style::allow_guessing) | |
194 | .options(group_opts) | |
195 | .positional(positional_options) | |
196 | .run(), vm); | |
197 | ||
198 | if (vm[at::POSITIONAL_COMMAND_SPEC].as<std::vector<std::string> >() != | |
199 | *matching_spec) { | |
200 | std::cerr << "rbd: failed to parse command" << std::endl; | |
201 | return EXIT_FAILURE; | |
202 | } | |
203 | ||
11fdf7f2 | 204 | int r = (*action->execute)(vm, ceph_global_init_args); |
7c673cae FG |
205 | if (r != 0) { |
206 | return std::abs(r); | |
207 | } | |
208 | } catch (po::required_option& e) { | |
209 | std::cerr << "rbd: " << e.what() << std::endl; | |
210 | return EXIT_FAILURE; | |
211 | } catch (po::too_many_positional_options_error& e) { | |
212 | std::cerr << "rbd: too many arguments" << std::endl; | |
213 | return EXIT_FAILURE; | |
214 | } catch (po::error& e) { | |
215 | std::cerr << "rbd: " << e.what() << std::endl; | |
216 | return EXIT_FAILURE; | |
217 | } | |
218 | ||
219 | return 0; | |
220 | } | |
221 | ||
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}; | |
228 | return; | |
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()); | |
235 | } | |
236 | return; | |
237 | } else if (arg[0] == '-') { | |
238 | // if the option is not a switch, skip its value | |
239 | if (arg.size() >= 2 && | |
240 | (arg[1] == '-' || | |
241 | get_switch_arguments().count(arg.substr(1, 1)) == 0) && | |
242 | (arg[1] != '-' || | |
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) { | |
246 | ++i; | |
247 | } | |
248 | } else { | |
249 | command_spec->push_back(arg); | |
250 | } | |
251 | } | |
252 | } | |
253 | ||
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; | |
265 | } | |
266 | *is_alias = false; | |
267 | return action; | |
268 | } | |
269 | } | |
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; | |
279 | } | |
280 | *is_alias = true; | |
281 | return action; | |
282 | } | |
283 | } | |
284 | } | |
285 | return NULL; | |
286 | } | |
287 | ||
288 | void Shell::get_global_options(po::options_description *opts) { | |
289 | opts->add_options() | |
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"); | |
299 | } | |
300 | ||
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; | |
306 | ||
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; }); | |
311 | ||
312 | std::cout << OptionPrinter::POSITIONAL_ARGUMENTS << ":" << std::endl | |
313 | << " <command>" << std::endl; | |
314 | ||
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()); | |
323 | } | |
324 | name_width += indent.size(); | |
325 | name_width = std::min(name_width, OptionPrinter::MAX_DESCRIPTION_OFFSET) + 1; | |
326 | ||
327 | for (size_t i = 0; i < actions.size(); ++i) { | |
328 | Action *action = actions[i]; | |
329 | if (!action->visible) | |
330 | continue; | |
331 | std::stringstream ss; | |
332 | ss << indent | |
333 | << format_command_name(action->command_spec, action->alias_command_spec); | |
334 | ||
335 | std::cout << ss.str(); | |
336 | if (!action->description.empty()) { | |
337 | IndentStream indent_stream(name_width, ss.str().size(), | |
338 | OptionPrinter::LINE_WIDTH, | |
339 | std::cout); | |
340 | indent_stream << action->description << std::endl; | |
341 | } else { | |
342 | std::cout << std::endl; | |
343 | } | |
344 | } | |
345 | ||
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; | |
351 | } | |
352 | ||
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(); | |
358 | ||
359 | po::options_description positional; | |
360 | po::options_description options; | |
361 | (*action->get_arguments)(&positional, &options); | |
362 | ||
363 | OptionPrinter option_printer(positional, options); | |
364 | option_printer.print_short(std::cout, ss.str().size()); | |
365 | ||
366 | if (!action->description.empty()) { | |
367 | std::cout << std::endl << action->description << std::endl; | |
368 | } | |
369 | ||
370 | std::cout << std::endl; | |
371 | option_printer.print_detailed(std::cout); | |
372 | ||
373 | if (!action->help.empty()) { | |
374 | std::cout << action->help << std::endl; | |
375 | } | |
376 | } | |
377 | ||
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; | |
383 | print_help(); | |
384 | } | |
385 | ||
386 | void Shell::print_bash_completion(const CommandSpec &command_spec) { | |
387 | ||
388 | bool is_alias = true; | |
389 | ||
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); | |
394 | ||
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); | |
400 | } else { | |
401 | std::cout << "|help"; | |
402 | for (size_t i = 0; i < get_actions().size(); ++i) { | |
403 | Action *action = get_actions()[i]; | |
404 | std::cout << "|" | |
405 | << joinify<std::string>(action->command_spec.begin(), | |
406 | action->command_spec.end(), " "); | |
407 | if (!action->alias_command_spec.empty()) { | |
408 | std::cout << "|" | |
409 | << joinify<std::string>(action->alias_command_spec.begin(), | |
410 | action->alias_command_spec.end(), | |
411 | " "); | |
412 | } | |
413 | } | |
414 | } | |
415 | std::cout << "|" << std::endl; | |
416 | } | |
417 | ||
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)); | |
424 | ||
425 | std::cout << "|--" << long_name << format_option_suffix(option); | |
426 | if (long_name != short_name) { | |
427 | std::cout << "|" << short_name << format_option_suffix(option); | |
428 | } | |
429 | } | |
430 | } | |
431 | ||
432 | } // namespace rbd |