]> git.proxmox.com Git - ceph.git/blame - ceph/src/tools/rbd/Shell.cc
update sources to ceph Nautilus 14.2.1
[ceph.git] / ceph / src / tools / rbd / Shell.cc
CommitLineData
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
17namespace rbd {
18
19namespace at = argument_types;
20namespace po = boost::program_options;
21
22namespace {
23
24static const std::string APP_NAME("rbd");
25static const std::string HELP_SPEC("help");
26static const std::string BASH_COMPLETION_SPEC("bash-completion");
27
11fdf7f2
TL
28boost::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
62std::string format_command_spec(const Shell::CommandSpec &spec) {
63 return joinify<std::string>(spec.begin(), spec.end(), " ");
64}
65
11fdf7f2
TL
66std::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
86std::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
95std::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
113std::vector<Shell::Action *>& Shell::get_actions() {
114 static std::vector<Action *> actions;
115
116 return actions;
117}
118
119std::set<std::string>& Shell::get_switch_arguments() {
120 static std::set<std::string> switch_arguments;
121
122 return switch_arguments;
123}
124
11fdf7f2
TL
125int 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
222void 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
254Shell::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
288void 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
301void 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
353void 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
378void 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
386void 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
418void 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