install(TARGETS librados librbd DESTINATION lib)
set(rbd_srcs
tools/rbd/rbd.cc
+ tools/rbd/IndentStream.cc
+ tools/rbd/OptionPrinter.cc
+ tools/rbd/Shell.cc
common/TextTable.cc)
add_executable(rbd ${rbd_srcs} $<TARGET_OBJECTS:common_util_obj>
$<TARGET_OBJECTS:parse_secret_objs>
if WITH_RBD
rbd_SOURCES = \
- tools/rbd/rbd.cc
-rbd_LDADD = $(LIBKRBD) $(LIBRBD) $(LIBRADOS) $(CEPH_GLOBAL)
+ tools/rbd/rbd.cc \
+ tools/rbd/IndentStream.cc \
+ tools/rbd/OptionPrinter.cc \
+ tools/rbd/Shell.cc
+noinst_HEADERS += \
+ tools/rbd/IndentStream.h \
+ tools/rbd/OptionPrinter.h \
+ tools/rbd/Shell.h
+rbd_LDADD = \
+ $(LIBKRBD) $(LIBRBD) $(LIBRADOS) $(CEPH_GLOBAL) \
+ $(BOOST_PROGRAM_OPTIONS_LIBS)
if LINUX
bin_PROGRAMS += rbd
endif # LINUX
--- /dev/null
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "tools/rbd/IndentStream.h"
+
+namespace rbd {
+
+int IndentBuffer::overflow (int c) {
+ if (traits_type::eq_int_type(traits_type::eof(), c)) {
+ return traits_type::not_eof(c);
+ }
+
+ int r;
+ switch (c) {
+ case '\n':
+ m_buffer += c;
+ flush_line();
+ r = m_streambuf->sputn(m_buffer.c_str(), m_buffer.size());
+ m_buffer.clear();
+ return r;
+ case '\t':
+ // convert tab to single space and fall-through
+ c = ' ';
+ default:
+ if (m_indent + m_buffer.size() >= m_line_length) {
+ size_t word_offset = m_buffer.find_last_of(m_delim);
+ bool space_delim = (m_delim == " ");
+ if (word_offset == std::string::npos && !space_delim) {
+ word_offset = m_buffer.find_last_of(" ");
+ }
+
+ if (word_offset != std::string::npos) {
+ flush_line();
+ m_streambuf->sputn(m_buffer.c_str(), word_offset);
+ m_buffer = std::string(m_buffer,
+ word_offset + (space_delim ? 1 : 0));
+ } else {
+ flush_line();
+ m_streambuf->sputn(m_buffer.c_str(), m_buffer.size());
+ m_buffer.clear();
+ }
+ m_streambuf->sputc('\n');
+ }
+ m_buffer += c;
+ return c;
+ }
+}
+
+void IndentBuffer::flush_line() {
+ if (m_initial_offset >= m_indent) {
+ m_initial_offset = 0;
+ m_streambuf->sputc('\n');
+ }
+
+ m_streambuf->sputn(m_indent_prefix.c_str(), m_indent - m_initial_offset);
+ m_initial_offset = 0;
+}
+
+} // namespace rbd
--- /dev/null
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_RBD_INDENT_STREAM_H
+#define CEPH_RBD_INDENT_STREAM_H
+
+#include "include/int_types.h"
+#include <iostream>
+#include <streambuf>
+#include <iomanip>
+
+namespace rbd {
+
+class IndentBuffer : public std::streambuf {
+public:
+ IndentBuffer(size_t indent, size_t initial_offset, size_t line_length,
+ std::streambuf *streambuf)
+ : m_indent(indent), m_initial_offset(initial_offset),
+ m_line_length(line_length), m_streambuf(streambuf),
+ m_delim(" "), m_indent_prefix(m_indent, ' ') {
+ }
+
+ void set_delimiter(const std::string &delim) {
+ m_delim = delim;
+ }
+
+protected:
+ virtual int overflow (int c);
+
+private:
+ size_t m_indent;
+ size_t m_initial_offset;
+ size_t m_line_length;
+ std::streambuf *m_streambuf;
+
+ std::string m_delim;
+ std::string m_indent_prefix;
+ std::string m_buffer;
+
+ void flush_line();
+};
+
+class IndentStream : public std::ostream {
+public:
+ IndentStream(size_t indent, size_t initial_offset, size_t line_length,
+ std::ostream &os)
+ : std::ostream(&m_indent_buffer),
+ m_indent_buffer(indent, initial_offset, line_length, os.rdbuf()) {
+ }
+
+ void set_delimiter(const std::string &delim) {
+ m_indent_buffer.set_delimiter(delim);
+ }
+private:
+ IndentBuffer m_indent_buffer;
+};
+
+} // namespace rbd
+
+#endif // CEPH_RBD_INDENT_STREAM_ITERATOR_H
--- /dev/null
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "tools/rbd/OptionPrinter.h"
+#include "tools/rbd/IndentStream.h"
+
+namespace rbd {
+
+namespace po = boost::program_options;
+
+const std::string OptionPrinter::POSITIONAL_ARGUMENTS("Positional arguments");
+const std::string OptionPrinter::OPTIONAL_ARGUMENTS("Optional arguments");
+
+OptionPrinter::OptionPrinter(const OptionsDescription &positional,
+ const OptionsDescription &optional)
+ : m_positional(positional), m_optional(optional) {
+}
+
+void OptionPrinter::print_short(std::ostream &os, size_t initial_offset) {
+ size_t name_width = std::min(initial_offset, MAX_DESCRIPTION_OFFSET) + 1;
+
+ IndentStream indent_stream(name_width, initial_offset, LINE_WIDTH, os);
+ indent_stream.set_delimiter("[");
+ for (size_t i = 0; i < m_optional.options().size(); ++i) {
+ bool required = m_optional.options()[i]->semantic()->is_required();
+ if (!required) {
+ indent_stream << "[";
+ }
+ indent_stream << "--" << m_optional.options()[i]->long_name();
+ if (m_optional.options()[i]->semantic()->max_tokens() != 0) {
+ indent_stream << " <" << m_optional.options()[i]->long_name() << ">";
+ }
+ if (!required) {
+ indent_stream << "]";
+ }
+ indent_stream << " ";
+ }
+ indent_stream << std::endl;
+
+ if (m_positional.options().size() > 0) {
+ indent_stream.set_delimiter(" ");
+ for (size_t i = 0; i < m_positional.options().size(); ++i) {
+ indent_stream << "<" << m_positional.options()[i]->long_name() << "> ";
+ if (m_positional.options()[i]->semantic()->max_tokens() > 1) {
+ indent_stream << "[<" << m_positional.options()[i]->long_name()
+ << "> ...]";
+ break;
+ }
+ }
+ indent_stream << std::endl;
+ }
+}
+
+void OptionPrinter::print_detailed(std::ostream &os) {
+ std::string indent_prefix(2, ' ');
+ size_t name_width = compute_name_width(indent_prefix.size());
+
+ if (m_positional.options().size() > 0) {
+ std::cout << POSITIONAL_ARGUMENTS << std::endl;
+ for (size_t i = 0; i < m_positional.options().size(); ++i) {
+ std::stringstream ss;
+ ss << indent_prefix << "<" << m_positional.options()[i]->long_name()
+ << ">";
+
+ std::cout << ss.str();
+ IndentStream indent_stream(name_width, ss.str().size(), LINE_WIDTH, os);
+ indent_stream << m_positional.options()[i]->description() << std::endl;
+ }
+ std::cout << std::endl;
+ }
+
+ if (m_optional.options().size() > 0) {
+ std::cout << OPTIONAL_ARGUMENTS << std::endl;
+ for (size_t i = 0; i < m_optional.options().size(); ++i) {
+ std::stringstream ss;
+ ss << indent_prefix
+ << m_optional.options()[i]->format_name() << " "
+ << m_optional.options()[i]->format_parameter();
+
+ std::cout << ss.str();
+ IndentStream indent_stream(name_width, ss.str().size(), LINE_WIDTH, os);
+ indent_stream << m_optional.options()[i]->description() << std::endl;
+ }
+ std::cout << std::endl;
+ }
+}
+
+size_t OptionPrinter::compute_name_width(size_t indent) {
+ size_t width = MIN_NAME_WIDTH;
+ std::vector<OptionsDescription> descs = {m_positional, m_optional};
+ for (size_t desc_idx = 0; desc_idx < descs.size(); ++desc_idx) {
+ const OptionsDescription &desc = descs[desc_idx];
+ for (size_t opt_idx = 0; opt_idx < desc.options().size(); ++opt_idx) {
+ size_t name_width = desc.options()[opt_idx]->format_name().size() +
+ desc.options()[opt_idx]->format_parameter().size()
+ + 1;
+ width = std::max(width, name_width);
+ }
+ }
+ width += indent;
+ width = std::min(width, MAX_DESCRIPTION_OFFSET) + 1;
+ return width;
+}
+
+} // namespace rbd
--- /dev/null
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_RBD_OPTION_PRINTER_H
+#define CEPH_RBD_OPTION_PRINTER_H
+
+#include "include/int_types.h"
+#include <string>
+#include <vector>
+#include <boost/program_options.hpp>
+
+namespace rbd {
+
+class OptionPrinter {
+public:
+ typedef boost::program_options::options_description OptionsDescription;
+
+ static const std::string POSITIONAL_ARGUMENTS;
+ static const std::string OPTIONAL_ARGUMENTS;
+
+ static const size_t LINE_WIDTH = 80;
+ static const size_t MIN_NAME_WIDTH = 20;
+ static const size_t MAX_DESCRIPTION_OFFSET = LINE_WIDTH / 2;
+
+ OptionPrinter(const OptionsDescription &positional,
+ const OptionsDescription &optional);
+
+ void print_short(std::ostream &os, size_t initial_offset);
+ void print_detailed(std::ostream &os);
+
+private:
+ const OptionsDescription &m_positional;
+ const OptionsDescription &m_optional;
+
+ size_t compute_name_width(size_t indent);
+};
+
+} // namespace rbd
+
+#endif // CEPH_RBD_OPTION_PRINTER_H
--- /dev/null
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include "tools/rbd/Shell.h"
+#include "tools/rbd/IndentStream.h"
+#include "tools/rbd/OptionPrinter.h"
+#include "common/config.h"
+#include "global/global_context.h"
+#include "include/stringify.h"
+#include <algorithm>
+#include <iostream>
+#include <set>
+
+namespace rbd {
+
+namespace po = boost::program_options;
+
+namespace {
+
+struct Secret {};
+
+void validate(boost::any& v, const std::vector<std::string>& values,
+ Secret *target_type, int) {
+ std::cerr << "rbd: --secret is deprecated, use --keyfile" << std::endl;
+
+ po::validators::check_first_occurrence(v);
+ const std::string &s = po::validators::get_single_string(values);
+ int r = g_conf->set_val("keyfile", s.c_str());
+ assert(r == 0);
+ v = boost::any(s);
+}
+
+std::string base_name(const std::string &path,
+ const std::string &delims = "/\\") {
+ return path.substr(path.find_last_of(delims) + 1);
+}
+
+std::string format_command_spec(const Shell::CommandSpec &spec) {
+ return joinify<std::string>(spec.begin(), spec.end(), " ");
+}
+
+std::string format_command_name(const Shell::CommandSpec &spec,
+ const Shell::CommandSpec &alias_spec) {
+ std::string name = format_command_spec(spec);
+ if (!alias_spec.empty()) {
+ name += " (" + format_command_spec(alias_spec) + ")";
+ }
+ return name;
+}
+
+} // anonymous namespace
+
+std::vector<Shell::Action *> Shell::s_actions;
+std::set<std::string> Shell::s_switch_arguments;
+
+int Shell::execute(int arg_count, const char **arg_values) {
+ std::string app_name(base_name(arg_values[0]));
+ std::vector<std::string> command_spec;
+ get_command_spec(arg_count, arg_values, &command_spec);
+
+ if (command_spec.empty() || command_spec == CommandSpec({"help"})) {
+ // list all available actions
+ print_help(app_name);
+ return 0;
+ } else if (command_spec[0] == "help") {
+ // list help for specific action
+ command_spec.erase(command_spec.begin());
+ Action *action = find_action(command_spec, NULL);
+ if (action == NULL) {
+ print_unknown_action(app_name, command_spec);
+ return EXIT_FAILURE;
+ } else {
+ print_action_help(app_name, action);
+ return 0;
+ }
+ }
+
+ CommandSpec *matching_spec;
+ Action *action = find_action(command_spec, &matching_spec);
+ if (action == NULL) {
+ print_unknown_action(app_name, command_spec);
+ return EXIT_FAILURE;
+ }
+
+ po::variables_map vm;
+ try {
+ po::options_description positional;
+ po::options_description options;
+ (*action->get_arguments)(&positional, &options);
+
+ // dynamically allocate options for our command (e.g. snap list) and
+ // its associated positional arguments
+ po::options_description arguments;
+ arguments.add_options()
+ ("positional-command-spec",
+ po::value<std::vector<std::string> >()->required(), "")
+ ("positional-arguments",
+ po::value<std::vector<std::string> >(), "");
+
+ po::positional_options_description positional_options;
+ positional_options.add("positional-command-spec",
+ matching_spec->size());
+ if (command_spec.size() > matching_spec->size()) {
+ positional_options.add("positional-arguments", -1);
+ }
+
+ po::options_description global;
+ get_global_options(&global);
+
+ po::options_description group;
+ group.add(options).add(arguments).add(global);
+
+ po::store(po::command_line_parser(arg_count, arg_values)
+ .style(po::command_line_style::default_style &
+ ~po::command_line_style::allow_guessing)
+ .options(group)
+ .positional(positional_options)
+ .allow_unregistered()
+ .run(), vm);
+
+ if (vm["positional-command-spec"].as<std::vector<std::string> >() !=
+ *matching_spec) {
+ std::cerr << "rbd: failed to parse command" << std::endl;
+ return EXIT_FAILURE;
+ }
+
+ po::notify(vm);
+
+ int r = (*action->execute)(vm);
+ if (r != 0) {
+ return std::abs(r);
+ }
+ } catch (po::required_option& e) {
+ std::cerr << "rbd: " << e.what() << std::endl << std::endl;
+ return EXIT_FAILURE;
+ } catch (po::too_many_positional_options_error& e) {
+ std::cerr << "rbd: too many positional arguments or unrecognized optional "
+ << "argument" << std::endl;
+ } catch (po::error& e) {
+ std::cerr << "rbd: " << e.what() << std::endl << std::endl;
+ return EXIT_FAILURE;
+ }
+
+ return 0;
+}
+
+void Shell::get_command_spec(int arg_count, const char **arg_values,
+ std::vector<std::string> *command_spec) {
+ for (int i = 1; i < arg_count; ++i) {
+ std::string arg(arg_values[i]);
+ if (arg == "-h" || arg == "--help") {
+ *command_spec = {"help"};
+ return;
+ } else if (arg[0] == '-') {
+ // if the option is not a switch, skip its value
+ if (arg.size() >= 2 &&
+ (arg[1] == '-' || s_switch_arguments.count(arg.substr(1, 1)) == 0) &&
+ (arg[1] != '-' ||
+ s_switch_arguments.count(arg.substr(2, std::string::npos)) == 0) &&
+ arg.find('=') == std::string::npos) {
+ ++i;
+ }
+ } else {
+ command_spec->push_back(arg);
+ }
+ }
+}
+
+Shell::Action *Shell::find_action(const CommandSpec &command_spec,
+ CommandSpec **matching_spec) {
+ for (size_t i = 0; i < s_actions.size(); ++i) {
+ Action *action = s_actions[i];
+ if (action->command_spec.size() <= command_spec.size()) {
+ if (std::includes(action->command_spec.begin(),
+ action->command_spec.end(),
+ command_spec.begin(),
+ command_spec.begin() + action->command_spec.size())) {
+ if (matching_spec != NULL) {
+ *matching_spec = &action->command_spec;
+ }
+ return action;
+ }
+ }
+ if (!action->alias_command_spec.empty() &&
+ action->alias_command_spec.size() <= command_spec.size()) {
+ if (std::includes(action->alias_command_spec.begin(),
+ action->alias_command_spec.end(),
+ command_spec.begin(),
+ command_spec.begin() +
+ action->alias_command_spec.size())) {
+ if (matching_spec != NULL) {
+ *matching_spec = &action->alias_command_spec;
+ }
+ return action;
+ }
+ }
+ }
+ return NULL;
+}
+
+void Shell::get_global_options(po::options_description *opts) {
+ opts->add_options()
+ ("conf,c", "path to cluster configuration")
+ ("cluster", "cluster name")
+ ("id,i", "client id (without 'client.' prefix)")
+ ("name,n", "client name")
+ ("secret", po::value<Secret>(), "path to secret key (deprecated)")
+ ("keyfile", "path to secret key")
+ ("keyring", "path to keyring");
+}
+
+void Shell::print_help(const std::string &app_name) {
+ std::cout << "usage: " << app_name << " <command> ..."
+ << std::endl << std::endl
+ << "Command-line interface for managing Ceph RBD images."
+ << std::endl << std::endl;
+
+ std::vector<Action *> actions(s_actions);
+ std::sort(actions.begin(), actions.end(),
+ [](Action *lhs, Action *rhs) { return lhs->command_spec <
+ rhs->command_spec; });
+
+ std::cout << OptionPrinter::POSITIONAL_ARGUMENTS << ":" << std::endl
+ << " <command>" << std::endl;
+
+ // since the commands have spaces, we have to build our own formatter
+ std::string indent(4, ' ');
+ size_t name_width = OptionPrinter::MIN_NAME_WIDTH;
+ for (size_t i = 0; i < actions.size(); ++i) {
+ Action *action = actions[i];
+ std::string name = format_command_name(action->command_spec,
+ action->alias_command_spec);
+ name_width = std::max(name_width, name.size());
+ }
+ name_width += indent.size();
+ name_width = std::min(name_width, OptionPrinter::MAX_DESCRIPTION_OFFSET) + 1;
+
+ for (size_t i = 0; i < actions.size(); ++i) {
+ Action *action = actions[i];
+ std::stringstream ss;
+ ss << indent
+ << format_command_name(action->command_spec, action->alias_command_spec);
+
+ std::cout << ss.str();
+ if (!action->description.empty()) {
+ IndentStream indent_stream(name_width, ss.str().size(),
+ OptionPrinter::LINE_WIDTH,
+ std::cout);
+ indent_stream << action->description << std::endl;
+ } else {
+ std::cout << std::endl;
+ }
+ }
+
+ po::options_description global_opts(OptionPrinter::OPTIONAL_ARGUMENTS);
+ get_global_options(&global_opts);
+ std::cout << std::endl << global_opts << std::endl
+ << "See '" << app_name << " help <command>' for help on a specific "
+ << "command." << std::endl;
+}
+
+void Shell::print_action_help(const std::string &app_name, Action *action) {
+
+ std::stringstream ss;
+ ss << "usage: " << app_name << " "
+ << format_command_spec(action->command_spec);
+ std::cout << ss.str();
+
+ po::options_description positional;
+ po::options_description options;
+ (*action->get_arguments)(&positional, &options);
+
+ OptionPrinter option_printer(positional, options);
+ option_printer.print_short(std::cout, ss.str().size());
+
+ if (!action->description.empty()) {
+ std::cout << std::endl << action->description << std::endl;
+ }
+
+ std::cout << std::endl;
+ option_printer.print_detailed(std::cout);
+}
+
+void Shell::print_unknown_action(const std::string &app_name,
+ const std::vector<std::string> &command_spec) {
+ std::cerr << "error: unknown option '"
+ << joinify<std::string>(command_spec.begin(),
+ command_spec.end(), " ") << "'"
+ << std::endl << std::endl;
+ print_help(app_name);
+}
+
+} // namespace rbd
--- /dev/null
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_RBD_SHELL_H
+#define CEPH_RBD_SHELL_H
+
+#include "include/int_types.h"
+#include <set>
+#include <string>
+#include <vector>
+#include <boost/program_options.hpp>
+
+namespace rbd {
+
+class Shell {
+public:
+ typedef std::vector<std::string> CommandSpec;
+
+ struct Action {
+ typedef void (*GetArguments)(boost::program_options::options_description *,
+ boost::program_options::options_description *);
+ typedef int (*Execute)(const boost::program_options::variables_map &);
+
+ CommandSpec command_spec;
+ CommandSpec alias_command_spec;
+ const std::string description;
+ const std::string help;
+ GetArguments get_arguments;
+ Execute execute;
+
+ template <typename Args, typename Execute>
+ Action(const std::initializer_list<std::string> &command_spec,
+ const std::initializer_list<std::string> &alias_command_spec,
+ const std::string &description, const std::string &help,
+ Args args, Execute execute)
+ : command_spec(command_spec), alias_command_spec(alias_command_spec),
+ description(description), help(help), get_arguments(args),
+ execute(execute) {
+ Shell::s_actions.push_back(this);
+ }
+
+ };
+
+ struct SwitchArguments {
+ SwitchArguments(const std::initializer_list<std::string> &arguments) {
+ Shell::s_switch_arguments.insert(arguments.begin(), arguments.end());
+ }
+ };
+
+ int execute(int arg_count, const char **arg_values);
+
+private:
+ static std::vector<Action *> s_actions;
+ static std::set<std::string> s_switch_arguments;
+
+ void get_command_spec(int arg_count, const char **arg_values,
+ std::vector<std::string> *command_spec);
+ Action *find_action(const CommandSpec &command_spec,
+ CommandSpec **matching_spec);
+
+ void get_global_options(boost::program_options::options_description *opts);
+
+ void print_help(const std::string &app_name);
+ void print_action_help(const std::string &app_name, Action *action);
+ void print_unknown_action(const std::string &app_name,
+ const std::vector<std::string> &command_spec);
+};
+
+} // namespace rbd
+
+#endif // CEPH_RBD_SHELL_H