]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
rbd: support libraries for switching CLI processing to boost
authorJason Dillaman <dillaman@redhat.com>
Fri, 18 Sep 2015 20:24:20 +0000 (16:24 -0400)
committerJason Dillaman <dillaman@redhat.com>
Thu, 5 Nov 2015 21:11:53 +0000 (16:11 -0500)
Added new classes for registering CLI commands and associated
arguments and dynamically generating help messages.

Signed-off-by: Jason Dillaman <dillaman@redhat.com>
src/CMakeLists.txt
src/tools/Makefile-client.am
src/tools/rbd/IndentStream.cc [new file with mode: 0644]
src/tools/rbd/IndentStream.h [new file with mode: 0644]
src/tools/rbd/OptionPrinter.cc [new file with mode: 0644]
src/tools/rbd/OptionPrinter.h [new file with mode: 0644]
src/tools/rbd/Shell.cc [new file with mode: 0644]
src/tools/rbd/Shell.h [new file with mode: 0644]

index 424a2c76c6f095bb3e192b5e92a122f2bf022f23..dbba59626f11a80576c452d88b227cd9bd3ef1a5 100644 (file)
@@ -876,6 +876,9 @@ if(${WITH_RBD})
   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>
index aa635d4612a8cc04ae0e4fb3d594c5d55f453568..6fd74da25133894d654f41cefad77815800c0aa0 100644 (file)
@@ -24,8 +24,17 @@ bin_PROGRAMS += rados
 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
diff --git a/src/tools/rbd/IndentStream.cc b/src/tools/rbd/IndentStream.cc
new file mode 100644 (file)
index 0000000..83591a8
--- /dev/null
@@ -0,0 +1,59 @@
+// -*- 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
diff --git a/src/tools/rbd/IndentStream.h b/src/tools/rbd/IndentStream.h
new file mode 100644 (file)
index 0000000..ba7d90b
--- /dev/null
@@ -0,0 +1,60 @@
+// -*- 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
diff --git a/src/tools/rbd/OptionPrinter.cc b/src/tools/rbd/OptionPrinter.cc
new file mode 100644 (file)
index 0000000..a0c6049
--- /dev/null
@@ -0,0 +1,105 @@
+// -*- 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
diff --git a/src/tools/rbd/OptionPrinter.h b/src/tools/rbd/OptionPrinter.h
new file mode 100644 (file)
index 0000000..e18a5f8
--- /dev/null
@@ -0,0 +1,40 @@
+// -*- 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
diff --git a/src/tools/rbd/Shell.cc b/src/tools/rbd/Shell.cc
new file mode 100644 (file)
index 0000000..7bb0a89
--- /dev/null
@@ -0,0 +1,293 @@
+// -*- 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
diff --git a/src/tools/rbd/Shell.h b/src/tools/rbd/Shell.h
new file mode 100644 (file)
index 0000000..a4502af
--- /dev/null
@@ -0,0 +1,71 @@
+// -*- 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