]> git.proxmox.com Git - ceph.git/blobdiff - ceph/src/tools/rbd/Utils.cc
update ceph source to reef 18.1.2
[ceph.git] / ceph / src / tools / rbd / Utils.cc
index 549cf9dd8338486d201002ce51baf3a3f1fe89e5..71da0bd274ac14f5815bb1620d9075ad1eb3af50 100644 (file)
@@ -2,7 +2,7 @@
 // vim: ts=8 sw=2 smarttab
 
 #include "tools/rbd/Utils.h"
-#include "include/assert.h"
+#include "include/ceph_assert.h"
 #include "include/Context.h"
 #include "include/encoding.h"
 #include "common/common_init.h"
 #include "include/rbd/features.h"
 #include "common/config.h"
 #include "common/errno.h"
+#include "common/escape.h"
 #include "common/safe_io.h"
 #include "global/global_context.h"
+#include <fstream>
 #include <iostream>
-#include <boost/regex.hpp>
+#include <regex>
 #include <boost/algorithm/string.hpp>
 #include <boost/lexical_cast.hpp>
 
@@ -23,13 +25,30 @@ namespace utils {
 namespace at = argument_types;
 namespace po = boost::program_options;
 
+namespace {
+
+static std::string mgr_command_args_to_str(
+    const std::map<std::string, std::string> &args) {
+  std::string out = "";
+
+  std::string delimiter;
+  for (auto &it : args) {
+    out += delimiter + "\"" + it.first + "\": \"" +
+      stringify(json_stream_escaper(it.second)) + "\"";
+    delimiter = ",\n";
+  }
+
+  return out;
+}
+
+} // anonymous namespace
+
 int ProgressContext::update_progress(uint64_t offset, uint64_t total) {
   if (progress) {
-    int pc = total ? (offset * 100ull / total) : 0;
-    if (pc != last_pc) {
-      cerr << "\r" << operation << ": "
-           << pc << "% complete...";
-      cerr.flush();
+    int pc = get_percentage(offset, total);
+    if (pc > last_pc) {
+      std::cerr << "\r" << operation << ": "
+               << pc << "% complete..." << std::flush;
       last_pc = pc;
     }
   }
@@ -38,17 +57,21 @@ int ProgressContext::update_progress(uint64_t offset, uint64_t total) {
 
 void ProgressContext::finish() {
   if (progress) {
-    cerr << "\r" << operation << ": 100% complete...done." << std::endl;
+    std::cerr << "\r" << operation << ": 100% complete...done." << std::endl;
   }
 }
 
 void ProgressContext::fail() {
   if (progress) {
-    cerr << "\r" << operation << ": " << last_pc << "% complete...failed."
-         << std::endl;
+    std::cerr << "\r" << operation << ": " << last_pc << "% complete...failed."
+             << std::endl;
   }
 }
 
+int get_percentage(uint64_t part, uint64_t whole) {
+  return whole ? (100 * part / whole) : 0;
+}
+
 void aio_context_callback(librbd::completion_t completion, void *arg)
 {
   librbd::RBD::AioCompletion *aio_completion =
@@ -67,9 +90,9 @@ int read_string(int fd, unsigned max, std::string *out) {
 
   bufferlist bl;
   bl.append(buf, 4);
-  bufferlist::iterator p = bl.begin();
+  auto p = bl.cbegin();
   uint32_t len;
-  ::decode(len, p);
+  decode(len, p);
   if (len > max)
     return -EINVAL;
 
@@ -82,90 +105,71 @@ int read_string(int fd, unsigned max, std::string *out) {
 }
 
 int extract_spec(const std::string &spec, std::string *pool_name,
-                 std::string *image_name, std::string *snap_name,
-                 SpecValidation spec_validation) {
-  if (!g_ceph_context->_conf->get_val<bool>("rbd_validate_names")) {
+                 std::string *namespace_name, std::string *name,
+                 std::string *snap_name, SpecValidation spec_validation) {
+  if (!g_ceph_context->_conf.get_val<bool>("rbd_validate_names")) {
     spec_validation = SPEC_VALIDATION_NONE;
   }
 
-  boost::regex pattern;
+  std::regex pattern;
   switch (spec_validation) {
   case SPEC_VALIDATION_FULL:
-    // disallow "/" and "@" in image and snap name
-    pattern = "^(?:([^/@]+)/)?([^/@]+)(?:@([^/@]+))?$";
+    // disallow "/" and "@" in all names
+    pattern = "^(?:([^/@]+)/(?:([^/@]+)/)?)?([^/@]+)(?:@([^/@]+))?$";
     break;
   case SPEC_VALIDATION_SNAP:
     // disallow "/" and "@" in snap name
-    pattern = "^(?:([^/]+)/)?([^@]+)(?:@([^/@]+))?$";
+    pattern = "^(?:([^/]+)/(?:([^/@]+)/)?)?([^@]+)(?:@([^/@]+))?$";
     break;
   case SPEC_VALIDATION_NONE:
-    // relaxed pattern assumes pool is before first "/" and snap
-    // name is after first "@"
-    pattern = "^(?:([^/]+)/)?([^@]+)(?:@(.+))?$";
+    // relaxed pattern assumes pool is before first "/",
+    // namespace is before second "/", and snap name is after first "@"
+    pattern = "^(?:([^/]+)/(?:([^/@]+)/)?)?([^@]+)(?:@(.+))?$";
     break;
   default:
-    assert(false);
+    ceph_abort();
     break;
   }
 
-  boost::smatch match;
-  if (!boost::regex_match(spec, match, pattern)) {
+  std::smatch match;
+  if (!std::regex_match(spec, match, pattern)) {
     std::cerr << "rbd: invalid spec '" << spec << "'" << std::endl;
     return -EINVAL;
   }
 
-  if (pool_name != nullptr && match[1].matched) {
-    *pool_name = match[1];
-  }
-  if (image_name != nullptr) {
-    *image_name = match[2];
-  }
-  if (snap_name != nullptr && match[3].matched) {
-    *snap_name = match[3];
-  }
-  return 0;
-}
-
-int extract_group_spec(const std::string &spec,
-                      std::string *pool_name,
-                      std::string *group_name) {
-  boost::regex pattern;
-  pattern = "^(?:([^/]+)/)?(.+)?$";
-
-  boost::smatch match;
-  if (!boost::regex_match(spec, match, pattern)) {
-    std::cerr << "rbd: invalid spec '" << spec << "'" << std::endl;
-    return -EINVAL;
+  if (match[1].matched) {
+    if (pool_name != nullptr) {
+      *pool_name = match[1];
+    } else {
+      std::cerr << "rbd: pool name specified for a command that doesn't use it"
+                << std::endl;
+      return -EINVAL;
+    }
   }
 
-  if (pool_name != nullptr && match[1].matched) {
-    *pool_name = match[1];
-  }
-  if (group_name != nullptr) {
-    *group_name = match[2];
+  if (match[2].matched) {
+    if (namespace_name != nullptr) {
+      *namespace_name = match[2];
+    } else {
+      std::cerr << "rbd: namespace name specified for a command that doesn't "
+                << "use it" << std::endl;
+      return -EINVAL;
+    }
   }
 
-  return 0;
-}
-
-int extract_image_id_spec(const std::string &spec, std::string *pool_name,
-                          std::string *image_id) {
-  boost::regex pattern;
-  pattern = "^(?:([^/]+)/)?(.+)?$";
-
-  boost::smatch match;
-  if (!boost::regex_match(spec, match, pattern)) {
-    std::cerr << "rbd: invalid spec '" << spec << "'" << std::endl;
-    return -EINVAL;
+  if (name != nullptr) {
+    *name = match[3];
   }
 
-  if (pool_name != nullptr && match[1].matched) {
-    *pool_name = match[1];
-  }
-  if (image_id != nullptr) {
-    *image_id = match[2];
+  if (match[4].matched) {
+    if (snap_name != nullptr) {
+      *snap_name = match[4];
+    } else {
+      std::cerr << "rbd: snapshot name specified for a command that doesn't "
+                << "use it" << std::endl;
+      return -EINVAL;
+    }
   }
-
   return 0;
 }
 
@@ -183,116 +187,51 @@ std::string get_positional_argument(const po::variables_map &vm, size_t index) {
   return "";
 }
 
-std::string get_default_pool_name() {
-  return g_ceph_context->_conf->get_val<std::string>("rbd_default_pool");
-}
-
-std::string get_pool_name(const po::variables_map &vm, size_t *arg_index) {
-  std::string pool_name;
-  if (vm.count(at::POOL_NAME)) {
-    pool_name = vm[at::POOL_NAME].as<std::string>();
-  } else {
-    pool_name = get_positional_argument(vm, *arg_index);
-    if (!pool_name.empty()) {
-       ++(*arg_index);
-    }
-  }
-
-  if (pool_name.empty()) {
-    pool_name = get_default_pool_name();
+void normalize_pool_name(std::string* pool_name) {
+  if (pool_name->empty()) {
+    *pool_name = get_default_pool_name();
   }
-  return pool_name;
 }
 
-int get_special_pool_group_names(const po::variables_map &vm,
-                                size_t *arg_index,
-                                std::string *group_pool_name,
-                                std::string *group_name) {
-  if (nullptr == group_pool_name) return -EINVAL;
-  if (nullptr == group_name) return -EINVAL;
-  std::string pool_key = at::POOL_NAME;
-
-  std::string group_pool_key = "group-" + at::POOL_NAME;
-  std::string group_key = at::GROUP_NAME;
-
-  if (vm.count(group_pool_key)) {
-    *group_pool_name = vm[group_pool_key].as<std::string>();
-  }
-
-  if (vm.count(group_key)) {
-    *group_name = vm[group_key].as<std::string>();
-  }
-
-  int r;
-  if (group_name->empty()) {
-    std::string spec = utils::get_positional_argument(vm, (*arg_index)++);
-    if (!spec.empty()) {
-      r = utils::extract_group_spec(spec, group_pool_name, group_name);
-      if (r < 0) {
-        return r;
-      }
-    }
-  }
-
-  if (group_pool_name->empty() && vm.count(pool_key)) {
-    *group_pool_name = vm[pool_key].as<std::string>();
-  }
-
-  if (group_pool_name->empty()) {
-    *group_pool_name = get_default_pool_name();
-  }
-
-  if (group_name->empty()) {
-    std::cerr << "rbd: consistency group name was not specified" << std::endl;
-    return -EINVAL;
-  }
-
-  return 0;
+std::string get_default_pool_name() {
+  return g_ceph_context->_conf.get_val<std::string>("rbd_default_pool");
 }
 
-int get_special_pool_image_names(const po::variables_map &vm,
-                                size_t *arg_index,
-                                std::string *image_pool_name,
-                                std::string *image_name) {
-  if (nullptr == image_pool_name) return -EINVAL;
-  if (nullptr == image_name) return -EINVAL;
-
-  std::string pool_key = at::POOL_NAME;
-
-  std::string image_pool_key = "image-" + at::POOL_NAME;
-  std::string image_key = at::IMAGE_NAME;
-
-  if (vm.count(image_pool_key)) {
-    *image_pool_name = vm[image_pool_key].as<std::string>();
+int get_pool_and_namespace_names(
+    const boost::program_options::variables_map &vm, bool validate_pool_name,
+    std::string* pool_name, std::string* namespace_name, size_t *arg_index) {
+  if (namespace_name != nullptr && vm.count(at::NAMESPACE_NAME)) {
+    *namespace_name = vm[at::NAMESPACE_NAME].as<std::string>();
   }
 
-  if (vm.count(image_key)) {
-    *image_name = vm[image_key].as<std::string>();
-  }
-
-  int r;
-  if (image_name->empty()) {
-    std::string spec = utils::get_positional_argument(vm, (*arg_index)++);
-    if (!spec.empty()) {
-      r = utils::extract_spec(spec, image_pool_name,
-                              image_name, nullptr,
-                             utils::SPEC_VALIDATION_NONE);
-      if (r < 0) {
-        return r;
+  if (vm.count(at::POOL_NAME)) {
+    *pool_name = vm[at::POOL_NAME].as<std::string>();
+  } else {
+    *pool_name = get_positional_argument(vm, *arg_index);
+    if (!pool_name->empty()) {
+      if (namespace_name != nullptr) {
+        auto slash_pos = pool_name->find_last_of('/');
+        if (slash_pos != std::string::npos) {
+          *namespace_name = pool_name->substr(slash_pos + 1);
+        }
+        *pool_name = pool_name->substr(0, slash_pos);
       }
+      ++(*arg_index);
     }
   }
 
-  if (image_pool_name->empty() && vm.count(pool_key)) {
-    *image_pool_name = vm[pool_key].as<std::string>();
-  }
-
-  if (image_pool_name->empty()) {
-    *image_pool_name = get_default_pool_name();
+  if (!g_ceph_context->_conf.get_val<bool>("rbd_validate_names")) {
+    validate_pool_name = false;
   }
 
-  if (image_name->empty()) {
-    std::cerr << "rbd: image name was not specified" << std::endl;
+  if (validate_pool_name &&
+      pool_name->find_first_of("/@") != std::string::npos) {
+    std::cerr << "rbd: invalid pool '" << *pool_name << "'" << std::endl;
+    return -EINVAL;
+  } else if (namespace_name != nullptr &&
+             namespace_name->find_first_of("/@") != std::string::npos) {
+    std::cerr << "rbd: invalid namespace '" << *namespace_name << "'"
+              << std::endl;
     return -EINVAL;
   }
 
@@ -300,13 +239,17 @@ int get_special_pool_image_names(const po::variables_map &vm,
 }
 
 int get_pool_image_id(const po::variables_map &vm,
-                     size_t *spec_arg_index,
-                     std::string *pool_name,
-                     std::string *image_id) {
+                      size_t *spec_arg_index,
+                      std::string *pool_name,
+                      std::string *namespace_name,
+                      std::string *image_id) {
 
   if (vm.count(at::POOL_NAME) && pool_name != nullptr) {
     *pool_name = vm[at::POOL_NAME].as<std::string>();
   }
+  if (vm.count(at::NAMESPACE_NAME) && namespace_name != nullptr) {
+    *namespace_name = vm[at::NAMESPACE_NAME].as<std::string>();
+  }
   if (vm.count(at::IMAGE_ID) && image_id != nullptr) {
     *image_id = vm[at::IMAGE_ID].as<std::string>();
   }
@@ -315,17 +258,14 @@ int get_pool_image_id(const po::variables_map &vm,
   if (image_id != nullptr && spec_arg_index != nullptr && image_id->empty()) {
     std::string spec = get_positional_argument(vm, (*spec_arg_index)++);
     if (!spec.empty()) {
-      r = extract_image_id_spec(spec, pool_name, image_id);
+      r = extract_spec(spec, pool_name, namespace_name, image_id, nullptr,
+                       SPEC_VALIDATION_FULL);
       if (r < 0) {
         return r;
       }
     }
   }
 
-  if (pool_name != nullptr && pool_name->empty()) {
-    *pool_name = get_default_pool_name();
-  }
-
   if (image_id != nullptr && image_id->empty()) {
     std::cerr << "rbd: image id was not specified" << std::endl;
     return -EINVAL;
@@ -334,261 +274,166 @@ int get_pool_image_id(const po::variables_map &vm,
   return 0;
 }
 
-int get_pool_group_names(const po::variables_map &vm,
-                        at::ArgumentModifier mod,
-                        size_t *spec_arg_index,
-                        std::string *pool_name,
-                        std::string *group_name) {
-  std::string pool_key = (mod == at::ARGUMENT_MODIFIER_DEST ?
-    at::DEST_POOL_NAME : at::POOL_NAME);
-  std::string group_key = (mod == at::ARGUMENT_MODIFIER_DEST ?
-    at::DEST_GROUP_NAME : at::GROUP_NAME);
-
-  if (vm.count(pool_key) && pool_name != nullptr) {
-    *pool_name = vm[pool_key].as<std::string>();
-  }
-  if (vm.count(group_key) && group_name != nullptr) {
-    *group_name = vm[group_key].as<std::string>();
+int get_image_or_snap_spec(const po::variables_map &vm, std::string *spec) {
+  size_t arg_index = 0;
+  std::string pool_name;
+  std::string nspace_name;
+  std::string image_name;
+  std::string snap_name;
+  int r = get_pool_image_snapshot_names(
+    vm, at::ARGUMENT_MODIFIER_NONE, &arg_index, &pool_name, &nspace_name,
+    &image_name, &snap_name, true, SNAPSHOT_PRESENCE_PERMITTED,
+    SPEC_VALIDATION_NONE);
+  if (r < 0) {
+    return r;
   }
 
-  int r;
-  if (group_name != nullptr && spec_arg_index != nullptr &&
-      group_name->empty()) {
-    std::string spec = get_positional_argument(vm, (*spec_arg_index)++);
-    if (!spec.empty()) {
-      r = extract_group_spec(spec, pool_name, group_name);
-      if (r < 0) {
-        return r;
-      }
+  if (pool_name.empty()) {
+    // connect to the cluster to get the default pool
+    librados::Rados rados;
+    r = init_rados(&rados);
+    if (r < 0) {
+      return r;
     }
-  }
 
-  if (pool_name != nullptr && pool_name->empty()) {
-    *pool_name = get_default_pool_name();
+    normalize_pool_name(&pool_name);
   }
 
-  if (group_name != nullptr && group_name->empty()) {
-    std::string prefix = at::get_description_prefix(mod);
-    std::cerr << "rbd: "
-              << (mod == at::ARGUMENT_MODIFIER_DEST ? prefix : std::string())
-              << "group name was not specified" << std::endl;
-    return -EINVAL;
+  spec->append(pool_name);
+  spec->append("/");
+  if (!nspace_name.empty()) {
+    spec->append(nspace_name);
+    spec->append("/");
+  }
+  spec->append(image_name);
+  if (!snap_name.empty()) {
+    spec->append("@");
+    spec->append(snap_name);
   }
 
   return 0;
 }
 
+void append_options_as_args(const std::vector<std::string> &options,
+                            std::vector<std::string> *args) {
+  for (auto &opts : options) {
+    std::vector<std::string> args_;
+    boost::split(args_, opts, boost::is_any_of(","));
+    for (auto &o : args_) {
+      args->push_back("--" + o);
+    }
+  }
+}
+
 int get_pool_image_snapshot_names(const po::variables_map &vm,
                                   at::ArgumentModifier mod,
                                   size_t *spec_arg_index,
                                   std::string *pool_name,
+                                  std::string *namespace_name,
                                   std::string *image_name,
                                   std::string *snap_name,
+                                  bool image_name_required,
                                   SnapshotPresence snapshot_presence,
-                                  SpecValidation spec_validation,
-                                  bool image_required) {
+                                  SpecValidation spec_validation) {
   std::string pool_key = (mod == at::ARGUMENT_MODIFIER_DEST ?
     at::DEST_POOL_NAME : at::POOL_NAME);
   std::string image_key = (mod == at::ARGUMENT_MODIFIER_DEST ?
     at::DEST_IMAGE_NAME : at::IMAGE_NAME);
+  return get_pool_generic_snapshot_names(vm, mod, spec_arg_index, pool_key,
+                                         pool_name, namespace_name, image_key,
+                                         "image", image_name, snap_name,
+                                         image_name_required, snapshot_presence,
+                                         spec_validation);
+}
+
+int get_pool_generic_snapshot_names(const po::variables_map &vm,
+                                    at::ArgumentModifier mod,
+                                    size_t *spec_arg_index,
+                                    const std::string& pool_key,
+                                    std::string *pool_name,
+                                    std::string *namespace_name,
+                                    const std::string& generic_key,
+                                    const std::string& generic_key_desc,
+                                    std::string *generic_name,
+                                    std::string *snap_name,
+                                    bool generic_name_required,
+                                    SnapshotPresence snapshot_presence,
+                                    SpecValidation spec_validation) {
+  std::string namespace_key = (mod == at::ARGUMENT_MODIFIER_DEST ?
+    at::DEST_NAMESPACE_NAME : at::NAMESPACE_NAME);
   std::string snap_key = (mod == at::ARGUMENT_MODIFIER_DEST ?
-       at::DEST_SNAPSHOT_NAME : at::SNAPSHOT_NAME);
+    at::DEST_SNAPSHOT_NAME : at::SNAPSHOT_NAME);
 
   if (vm.count(pool_key) && pool_name != nullptr) {
     *pool_name = vm[pool_key].as<std::string>();
   }
-  if (vm.count(image_key) && image_name != nullptr) {
-    *image_name = vm[image_key].as<std::string>();
+  if (vm.count(namespace_key) && namespace_name != nullptr) {
+    *namespace_name = vm[namespace_key].as<std::string>();
+  }
+  if (vm.count(generic_key) && generic_name != nullptr) {
+    *generic_name = vm[generic_key].as<std::string>();
   }
   if (vm.count(snap_key) && snap_name != nullptr) {
-     *snap_name = vm[snap_key].as<std::string>();
+    *snap_name = vm[snap_key].as<std::string>();
   }
 
   int r;
-  if (image_name != nullptr && !image_name->empty()) {
+  if ((generic_key == at::IMAGE_NAME || generic_key == at::DEST_IMAGE_NAME) &&
+      generic_name != nullptr && !generic_name->empty()) {
     // despite the separate pool and snapshot name options,
     // we can also specify them via the image option
-    std::string image_name_copy(*image_name);
-    r = extract_spec(image_name_copy, pool_name, image_name, snap_name,
-                     spec_validation);
+    std::string image_name_copy(*generic_name);
+    r = extract_spec(image_name_copy, pool_name, namespace_name, generic_name,
+                     snap_name, spec_validation);
     if (r < 0) {
       return r;
     }
   }
 
-  if (image_name != nullptr && spec_arg_index != nullptr &&
-      image_name->empty()) {
+  if (generic_name != nullptr && spec_arg_index != nullptr &&
+      generic_name->empty()) {
     std::string spec = get_positional_argument(vm, (*spec_arg_index)++);
     if (!spec.empty()) {
-      r = extract_spec(spec, pool_name, image_name, snap_name, spec_validation);
+      r = extract_spec(spec, pool_name, namespace_name, generic_name, snap_name,
+                       spec_validation);
       if (r < 0) {
         return r;
       }
     }
   }
 
-  if (pool_name != nullptr && pool_name->empty()) {
-    *pool_name = get_default_pool_name();
-  }
-
-  if (image_name != nullptr && image_required && image_name->empty()) {
+  if (generic_name != nullptr && generic_name_required &&
+      generic_name->empty()) {
     std::string prefix = at::get_description_prefix(mod);
     std::cerr << "rbd: "
               << (mod == at::ARGUMENT_MODIFIER_DEST ? prefix : std::string())
-              << "image name was not specified" << std::endl;
+              << generic_key_desc << " name was not specified" << std::endl;
     return -EINVAL;
   }
 
-  //Validate pool name while creating/renaming/copying/cloning/importing/etc
+  std::regex pattern("^[^@/]*?$");
   if (spec_validation == SPEC_VALIDATION_FULL) {
-    boost::regex pattern("^[^@/]+?$");
-    if (!boost::regex_match (*pool_name, pattern)) {
+    // validate pool name while creating/renaming/copying/cloning/importing/etc
+    if ((pool_name != nullptr) && !std::regex_match (*pool_name, pattern)) {
       std::cerr << "rbd: invalid pool name '" << *pool_name << "'" << std::endl;
       return -EINVAL;
     }
   }
 
-  if (snap_name != nullptr) {
-    r = validate_snapshot_name(mod, *snap_name, snapshot_presence,
-                              spec_validation);
-    if (r < 0) {
-      return r;
-    }
-  }
-  return 0;
-}
-
-int get_pool_snapshot_names(const po::variables_map &vm,
-                            at::ArgumentModifier mod,
-                            size_t *spec_arg_index,
-                            std::string *pool_name,
-                            std::string *snap_name,
-                            SnapshotPresence snapshot_presence,
-                            SpecValidation spec_validation) {
-  std::string pool_key = (mod == at::ARGUMENT_MODIFIER_DEST ?
-    at::DEST_POOL_NAME : at::POOL_NAME);
-  std::string snap_key = (mod == at::ARGUMENT_MODIFIER_DEST ?
-       at::DEST_SNAPSHOT_NAME : at::SNAPSHOT_NAME);
-
-  if (vm.count(pool_key) && pool_name != nullptr) {
-    *pool_name = vm[pool_key].as<std::string>();
-  }
-  if (vm.count(snap_key) && snap_name != nullptr) {
-     *snap_name = vm[snap_key].as<std::string>();
-  }
-
-  if (pool_name != nullptr && pool_name->empty()) {
-    *pool_name = get_default_pool_name();
+  if (namespace_name != nullptr && !namespace_name->empty() &&
+      !std::regex_match (*namespace_name, pattern)) {
+    std::cerr << "rbd: invalid namespace name '" << *namespace_name << "'"
+              << std::endl;
+    return -EINVAL;
   }
 
   if (snap_name != nullptr) {
-    int r = validate_snapshot_name(mod, *snap_name, snapshot_presence,
-                                   spec_validation);
-    if (r < 0) {
-      return r;
-    }
-  }
-  return 0;
-}
-
-int get_pool_journal_names(const po::variables_map &vm,
-                          at::ArgumentModifier mod,
-                          size_t *spec_arg_index,
-                          std::string *pool_name,
-                          std::string *journal_name) {
-  std::string pool_key = (mod == at::ARGUMENT_MODIFIER_DEST ?
-    at::DEST_POOL_NAME : at::POOL_NAME);
-  std::string image_key = (mod == at::ARGUMENT_MODIFIER_DEST ?
-    at::DEST_IMAGE_NAME : at::IMAGE_NAME);
-  std::string journal_key = (mod == at::ARGUMENT_MODIFIER_DEST ?
-    at::DEST_JOURNAL_NAME : at::JOURNAL_NAME);
-
-  if (vm.count(pool_key) && pool_name != nullptr) {
-    *pool_name = vm[pool_key].as<std::string>();
-  }
-  if (vm.count(journal_key) && journal_name != nullptr) {
-    *journal_name = vm[journal_key].as<std::string>();
-  }
-
-  std::string image_name;
-  if (vm.count(image_key)) {
-    image_name = vm[image_key].as<std::string>();
-  }
-
-  int r;
-  if (journal_name != nullptr && !journal_name->empty()) {
-    // despite the separate pool option,
-    // we can also specify them via the journal option
-    std::string journal_name_copy(*journal_name);
-    r = extract_spec(journal_name_copy, pool_name, journal_name, nullptr,
-                     SPEC_VALIDATION_FULL);
-    if (r < 0) {
-      return r;
-    }
-  }
-
-  if (!image_name.empty()) {
-    // despite the separate pool option,
-    // we can also specify them via the image option
-    std::string image_name_copy(image_name);
-    r = extract_spec(image_name_copy, pool_name, &image_name, nullptr,
-                     SPEC_VALIDATION_NONE);
-    if (r < 0) {
-      return r;
-    }
-  }
-
-  if (journal_name != nullptr && spec_arg_index != nullptr &&
-      journal_name->empty()) {
-    std::string spec = get_positional_argument(vm, (*spec_arg_index)++);
-    if (!spec.empty()) {
-      r = extract_spec(spec, pool_name, journal_name, nullptr,
-                       SPEC_VALIDATION_FULL);
-      if (r < 0) {
-        return r;
-      }
-    }
-  }
-
-  if (pool_name != nullptr && pool_name->empty()) {
-    *pool_name = get_default_pool_name();
-  }
-
-  if (pool_name != nullptr && journal_name != nullptr &&
-      journal_name->empty() && !image_name.empty()) {
-    // Try to get journal name from image info.
-    librados::Rados rados;
-    librados::IoCtx io_ctx;
-    librbd::Image image;
-    int r = init_and_open_image(*pool_name, image_name, "", "", true, &rados,
-                                &io_ctx, &image);
-    if (r < 0) {
-      std::cerr << "rbd: failed to open image " << image_name
-               << " to get journal name: " << cpp_strerror(r) << std::endl;
-      return r;
-    }
-
-    uint64_t features;
-    r = image.features(&features);
+    r = validate_snapshot_name(mod, *snap_name, snapshot_presence,
+                              spec_validation);
     if (r < 0) {
       return r;
     }
-    if ((features & RBD_FEATURE_JOURNALING) == 0) {
-      std::cerr << "rbd: journaling is not enabled for image " << image_name
-               << std::endl;
-      return -EINVAL;
-    }
-    *journal_name = image_id(image);
   }
-
-  if (journal_name != nullptr && journal_name->empty()) {
-    std::string prefix = at::get_description_prefix(mod);
-    std::cerr << "rbd: "
-              << (mod == at::ARGUMENT_MODIFIER_DEST ? prefix : std::string())
-              << "journal was not specified" << std::endl;
-    return -EINVAL;
-  }
-
   return 0;
 }
 
@@ -604,7 +449,7 @@ int validate_snapshot_name(at::ArgumentModifier mod,
     if (!snap_name.empty()) {
       std::cerr << "rbd: "
                 << (mod == at::ARGUMENT_MODIFIER_DEST ? prefix : std::string())
-                << "snapname specified for a command that doesn't use it"
+                << "snapshot name specified for a command that doesn't use it"
                 << std::endl;
       return -EINVAL;
     }
@@ -613,7 +458,7 @@ int validate_snapshot_name(at::ArgumentModifier mod,
     if (snap_name.empty()) {
       std::cerr << "rbd: "
                 << (mod == at::ARGUMENT_MODIFIER_DEST ? prefix : std::string())
-                << "snap name was not specified" << std::endl;
+                << "snapshot name was not specified" << std::endl;
       return -EINVAL;
     }
     break;
@@ -621,8 +466,8 @@ int validate_snapshot_name(at::ArgumentModifier mod,
 
   if (spec_validation == SPEC_VALIDATION_SNAP) {
     // disallow "/" and "@" in snap name
-    boost::regex pattern("^[^@/]*?$");
-    if (!boost::regex_match (snap_name, pattern)) {
+    std::regex pattern("^[^@/]*?$");
+    if (!std::regex_match (snap_name, pattern)) {
       std::cerr << "rbd: invalid snap name '" << snap_name << "'" << std::endl;
       return -EINVAL;
     }
@@ -633,21 +478,18 @@ int validate_snapshot_name(at::ArgumentModifier mod,
 int get_image_options(const boost::program_options::variables_map &vm,
                      bool get_format, librbd::ImageOptions *opts) {
   uint64_t order = 0, stripe_unit = 0, stripe_count = 0, object_size = 0;
-  uint64_t features = 0, features_clear = 0, features_set = 0;
+  uint64_t features = 0, features_clear = 0;
   std::string data_pool;
   bool order_specified = true;
   bool features_specified = false;
   bool features_clear_specified = false;
-  bool features_set_specified = false;
   bool stripe_specified = false;
 
   if (vm.count(at::IMAGE_ORDER)) {
     order = vm[at::IMAGE_ORDER].as<uint64_t>();
-    std::cerr << "rbd: --order is deprecated, use --object-size"
-             << std::endl;
   } else if (vm.count(at::IMAGE_OBJECT_SIZE)) {
     object_size = vm[at::IMAGE_OBJECT_SIZE].as<uint64_t>();
-    order = std::round(std::log2(object_size)); 
+    order = std::round(std::log2(object_size));
   } else {
     order_specified = false;
   }
@@ -655,8 +497,6 @@ int get_image_options(const boost::program_options::variables_map &vm,
   if (vm.count(at::IMAGE_FEATURES)) {
     features = vm[at::IMAGE_FEATURES].as<uint64_t>();
     features_specified = true;
-  } else {
-    features = get_rbd_default_features(g_ceph_context);
   }
 
   if (vm.count(at::IMAGE_STRIPE_UNIT)) {
@@ -731,8 +571,8 @@ int get_image_options(const boost::program_options::variables_map &vm,
     }
 
     if (format_specified) {
-      int r = g_conf->set_val("rbd_default_format", stringify(format));
-      assert(r == 0);
+      int r = g_conf().set_val("rbd_default_format", stringify(format));
+      ceph_assert(r == 0);
       opts->set(RBD_IMAGE_OPTION_FORMAT, format);
     }
   }
@@ -744,8 +584,6 @@ int get_image_options(const boost::program_options::variables_map &vm,
   if (features_clear_specified) {
     opts->set(RBD_IMAGE_OPTION_FEATURES_CLEAR, features_clear);
   }
-  if (features_set_specified)
-    opts->set(RBD_IMAGE_OPTION_FEATURES_SET, features_set);
   if (stripe_specified) {
     opts->set(RBD_IMAGE_OPTION_STRIPE_UNIT, stripe_unit);
     opts->set(RBD_IMAGE_OPTION_STRIPE_COUNT, stripe_count);
@@ -758,6 +596,16 @@ int get_image_options(const boost::program_options::variables_map &vm,
     return r;
   }
 
+  r = get_flatten_option(vm, opts);
+  if (r < 0) {
+    return r;
+  }
+
+  if (vm.count(at::IMAGE_MIRROR_IMAGE_MODE)) {
+    opts->set(RBD_IMAGE_OPTION_MIRROR_IMAGE_MODE,
+              vm[at::IMAGE_MIRROR_IMAGE_MODE].as<librbd::mirror_image_mode_t>());
+  }
+
   return 0;
 }
 
@@ -772,30 +620,39 @@ int get_journal_options(const boost::program_options::variables_map &vm,
     }
     opts->set(RBD_IMAGE_OPTION_JOURNAL_ORDER, order);
 
-    int r = g_conf->set_val("rbd_journal_order", stringify(order));
-    assert(r == 0);
+    int r = g_conf().set_val("rbd_journal_order", stringify(order));
+    ceph_assert(r == 0);
   }
   if (vm.count(at::JOURNAL_SPLAY_WIDTH)) {
     opts->set(RBD_IMAGE_OPTION_JOURNAL_SPLAY_WIDTH,
              vm[at::JOURNAL_SPLAY_WIDTH].as<uint64_t>());
 
-    int r = g_conf->set_val("rbd_journal_splay_width",
+    int r = g_conf().set_val("rbd_journal_splay_width",
                            stringify(
                              vm[at::JOURNAL_SPLAY_WIDTH].as<uint64_t>()));
-    assert(r == 0);
+    ceph_assert(r == 0);
   }
   if (vm.count(at::JOURNAL_POOL)) {
     opts->set(RBD_IMAGE_OPTION_JOURNAL_POOL,
              vm[at::JOURNAL_POOL].as<std::string>());
 
-    int r = g_conf->set_val("rbd_journal_pool",
+    int r = g_conf().set_val("rbd_journal_pool",
                            vm[at::JOURNAL_POOL].as<std::string>());
-    assert(r == 0);
+    ceph_assert(r == 0);
   }
 
   return 0;
 }
 
+int get_flatten_option(const boost::program_options::variables_map &vm,
+                       librbd::ImageOptions *opts) {
+  if (vm.count(at::IMAGE_FLATTEN) && vm[at::IMAGE_FLATTEN].as<bool>()) {
+    uint64_t flatten = 1;
+    opts->set(RBD_IMAGE_OPTION_FLATTEN, flatten);
+  }
+  return 0;
+}
+
 int get_image_size(const boost::program_options::variables_map &vm,
                    uint64_t *size) {
   if (vm.count(at::IMAGE_SIZE) == 0) {
@@ -808,11 +665,14 @@ int get_image_size(const boost::program_options::variables_map &vm,
 }
 
 int get_path(const boost::program_options::variables_map &vm,
-             const std::string &positional_path, std::string *path) {
-  if (!positional_path.empty()) {
-    *path = positional_path;
-  } else if (vm.count(at::PATH)) {
+             size_t *arg_index, std::string *path) {
+  if (vm.count(at::PATH)) {
     *path = vm[at::PATH].as<std::string>();
+  } else {
+    *path = get_positional_argument(vm, *arg_index);
+    if (!path->empty()) {
+      ++(*arg_index);
+    }
   }
 
   if (path->empty()) {
@@ -842,14 +702,96 @@ int get_formatter(const po::variables_map &vm,
   return 0;
 }
 
+int get_snap_create_flags(const po::variables_map &vm, uint32_t *flags) {
+  if (vm[at::SKIP_QUIESCE].as<bool>() &&
+      vm[at::IGNORE_QUIESCE_ERROR].as<bool>()) {
+    std::cerr << "rbd: " << at::IGNORE_QUIESCE_ERROR
+              << " cannot be used together with " << at::SKIP_QUIESCE
+              << std::endl;
+    return -EINVAL;
+  }
+
+  *flags = 0;
+  if (vm[at::SKIP_QUIESCE].as<bool>()) {
+    *flags |= RBD_SNAP_CREATE_SKIP_QUIESCE;
+  } else if (vm[at::IGNORE_QUIESCE_ERROR].as<bool>()) {
+    *flags |= RBD_SNAP_CREATE_IGNORE_QUIESCE_ERROR;
+  }
+  return 0;
+}
+
+int get_encryption_options(const boost::program_options::variables_map &vm,
+                           EncryptionOptions* result) {
+  std::vector<std::string> passphrase_files;
+  if (vm.count(at::ENCRYPTION_PASSPHRASE_FILE)) {
+    passphrase_files =
+            vm[at::ENCRYPTION_PASSPHRASE_FILE].as<std::vector<std::string>>();
+  }
+
+  std::vector<at::EncryptionFormat> formats;
+  if (vm.count(at::ENCRYPTION_FORMAT)) {
+    formats = vm[at::ENCRYPTION_FORMAT].as<decltype(formats)>();
+  } else if (vm.count(at::ENCRYPTION_PASSPHRASE_FILE)) {
+    formats.resize(passphrase_files.size(),
+                   at::EncryptionFormat{RBD_ENCRYPTION_FORMAT_LUKS});
+  }
+
+  if (formats.size() != passphrase_files.size()) {
+    std::cerr << "rbd: encryption formats count does not match "
+              << "passphrase files count" << std::endl;
+    return -EINVAL;
+  }
+
+  result->specs.clear();
+  result->specs.reserve(formats.size());
+  for (size_t i = 0; i < formats.size(); ++i) {
+    std::ifstream file(passphrase_files[i], std::ios::in | std::ios::binary);
+    if (file.fail()) {
+      std::cerr << "rbd: unable to open passphrase file '"
+                << passphrase_files[i] << "': " << cpp_strerror(errno)
+                << std::endl;
+      return -errno;
+    }
+    std::string passphrase((std::istreambuf_iterator<char>(file)),
+                           std::istreambuf_iterator<char>());
+    file.close();
+
+    switch (formats[i].format) {
+    case RBD_ENCRYPTION_FORMAT_LUKS: {
+      auto opts = new librbd::encryption_luks_format_options_t{
+          std::move(passphrase)};
+      result->specs.push_back(
+          {RBD_ENCRYPTION_FORMAT_LUKS, opts, sizeof(*opts)});
+      break;
+    }
+    case RBD_ENCRYPTION_FORMAT_LUKS1: {
+      auto opts = new librbd::encryption_luks1_format_options_t{
+          .passphrase = std::move(passphrase)};
+      result->specs.push_back(
+          {RBD_ENCRYPTION_FORMAT_LUKS1, opts, sizeof(*opts)});
+      break;
+    }
+    case RBD_ENCRYPTION_FORMAT_LUKS2: {
+      auto opts = new librbd::encryption_luks2_format_options_t{
+          .passphrase = std::move(passphrase)};
+      result->specs.push_back(
+          {RBD_ENCRYPTION_FORMAT_LUKS2, opts, sizeof(*opts)});
+      break;
+    }
+    default:
+      ceph_abort();
+    }
+  }
+
+  return 0;
+}
+
 void init_context() {
-  g_conf->set_val_or_die("rbd_cache_writethrough_until_flush", "false");
-  g_conf->apply_changes(NULL);
-  common_init_finish(g_ceph_context);
+  g_conf().set_val_or_die("rbd_cache_writethrough_until_flush", "false");
+  g_conf().apply_changes(nullptr);
 }
 
-int init(const std::string &pool_name, librados::Rados *rados,
-         librados::IoCtx *io_ctx) {
+int init_rados(librados::Rados *rados) {
   init_context();
 
   int r = rados->init_with_context(g_ceph_context);
@@ -864,15 +806,29 @@ int init(const std::string &pool_name, librados::Rados *rados,
     return r;
   }
 
-  r = init_io_ctx(*rados, pool_name, io_ctx);
+  return 0;
+}
+
+int init(const std::string &pool_name, const std::string& namespace_name,
+         librados::Rados *rados, librados::IoCtx *io_ctx) {
+  init_context();
+
+  int r = init_rados(rados);
+  if (r < 0) {
+    return r;
+  }
+
+  r = init_io_ctx(*rados, pool_name, namespace_name, io_ctx);
   if (r < 0) {
     return r;
   }
   return 0;
 }
 
-int init_io_ctx(librados::Rados &rados, const std::string &pool_name,
-                librados::IoCtx *io_ctx) {
+int init_io_ctx(librados::Rados &rados, std::string pool_name,
+                const std::string& namespace_name, librados::IoCtx *io_ctx) {
+  normalize_pool_name(&pool_name);
+
   int r = rados.ioctx_create(pool_name.c_str(), *io_ctx);
   if (r < 0) {
     if (r == -ENOENT && pool_name == get_default_pool_name()) {
@@ -886,9 +842,34 @@ int init_io_ctx(librados::Rados &rados, const std::string &pool_name,
     }
     return r;
   }
+
+  return set_namespace(namespace_name, io_ctx);
+}
+
+int set_namespace(const std::string& namespace_name, librados::IoCtx *io_ctx) {
+  if (!namespace_name.empty()) {
+    librbd::RBD rbd;
+    bool exists = false;
+    int r = rbd.namespace_exists(*io_ctx, namespace_name.c_str(), &exists);
+    if (r < 0) {
+      std::cerr << "rbd: error asserting namespace: "
+                << cpp_strerror(r) << std::endl;
+      return r;
+    }
+    if (!exists) {
+      std::cerr << "rbd: namespace '" << namespace_name << "' does not exist."
+                << std::endl;
+      return -ENOENT;
+    }
+  }
+  io_ctx->set_namespace(namespace_name);
   return 0;
 }
 
+void disable_cache() {
+  g_conf().set_val_or_die("rbd_cache", "false");
+}
+
 int open_image(librados::IoCtx &io_ctx, const std::string &image_name,
                bool read_only, librbd::Image *image) {
   int r;
@@ -926,12 +907,13 @@ int open_image_by_id(librados::IoCtx &io_ctx, const std::string &image_id,
 }
 
 int init_and_open_image(const std::string &pool_name,
+                        const std::string &namespace_name,
                         const std::string &image_name,
                         const std::string &image_id,
                         const std::string &snap_name, bool read_only,
                         librados::Rados *rados, librados::IoCtx *io_ctx,
                         librbd::Image *image) {
-  int r = init(pool_name, rados, io_ctx);
+  int r = init(pool_name, namespace_name, rados, io_ctx);
   if (r < 0) {
     return r;
   }
@@ -951,6 +933,7 @@ int init_and_open_image(const std::string &pool_name,
       return r;
     }
   }
+
   return 0;
 }
 
@@ -972,7 +955,7 @@ void calc_sparse_extent(const bufferptr &bp,
                         bool *zeroed) {
   if (sparse_size == 0) {
     // sparse writes are disabled -- write the full extent
-    assert(buffer_offset == 0);
+    ceph_assert(buffer_offset == 0);
     *write_length = buffer_length;
     *zeroed = false;
     return;
@@ -990,7 +973,7 @@ void calc_sparse_extent(const bufferptr &bp,
     if (original_offset == buffer_offset) {
       *zeroed = extent_is_zero;
     } else if (*zeroed != extent_is_zero) {
-      assert(*write_length > 0);
+      ceph_assert(*write_length > 0);
       return;
     }
 
@@ -1008,6 +991,17 @@ std::string image_id(librbd::Image& image) {
   return id;
 }
 
+std::string mirror_image_mode(librbd::mirror_image_mode_t mode) {
+  switch (mode) {
+    case RBD_MIRROR_IMAGE_MODE_JOURNAL:
+      return "journal";
+    case RBD_MIRROR_IMAGE_MODE_SNAPSHOT:
+      return "snapshot";
+    default:
+      return "unknown";
+  }
+}
+
 std::string mirror_image_state(librbd::mirror_image_state_t state) {
   switch (state) {
     case RBD_MIRROR_IMAGE_DISABLING:
@@ -1021,7 +1015,8 @@ std::string mirror_image_state(librbd::mirror_image_state_t state) {
   }
 }
 
-std::string mirror_image_status_state(librbd::mirror_image_status_state_t state) {
+std::string mirror_image_status_state(
+    librbd::mirror_image_status_state_t state) {
   switch (state) {
   case MIRROR_IMAGE_STATUS_STATE_UNKNOWN:
     return "unknown";
@@ -1042,43 +1037,166 @@ std::string mirror_image_status_state(librbd::mirror_image_status_state_t state)
   }
 }
 
-std::string mirror_image_status_state(librbd::mirror_image_status_t status) {
+std::string mirror_image_site_status_state(
+    const librbd::mirror_image_site_status_t& status) {
   return (status.up ? "up+" : "down+") +
     mirror_image_status_state(status.state);
 }
 
+std::string mirror_image_global_status_state(
+    const librbd::mirror_image_global_status_t& status) {
+  librbd::mirror_image_site_status_t local_status;
+  int r = get_local_mirror_image_status(status, &local_status);
+  if (r < 0) {
+    return "down+unknown";
+  }
+
+  return mirror_image_site_status_state(local_status);
+}
+
+int get_local_mirror_image_status(
+    const librbd::mirror_image_global_status_t& status,
+    librbd::mirror_image_site_status_t* local_status) {
+  auto it = std::find_if(status.site_statuses.begin(),
+                         status.site_statuses.end(),
+                         [](auto& site_status) {
+      return (site_status.mirror_uuid ==
+                RBD_MIRROR_IMAGE_STATUS_LOCAL_MIRROR_UUID);
+    });
+  if (it == status.site_statuses.end()) {
+    return -ENOENT;
+  }
+
+  *local_status = *it;
+  return 0;
+}
+
 std::string timestr(time_t t) {
+  if (t == 0) {
+    return "";
+  }
+
   struct tm tm;
 
   localtime_r(&t, &tm);
 
   char buf[32];
-  strftime(buf, sizeof(buf), "%F %T", &tm);
+  strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", &tm);
 
   return buf;
 }
 
 uint64_t get_rbd_default_features(CephContext* cct) {
-  auto features = cct->_conf->get_val<std::string>("rbd_default_features");
+  auto features = cct->_conf.get_val<std::string>("rbd_default_features");
   return boost::lexical_cast<uint64_t>(features);
 }
 
-bool check_if_image_spec_present(const po::variables_map &vm,
-                                 at::ArgumentModifier mod,
-                                 size_t spec_arg_index) {
-  std::string image_key = (mod == at::ARGUMENT_MODIFIER_DEST ?
-    at::DEST_IMAGE_NAME : at::IMAGE_NAME);
+bool is_not_user_snap_namespace(librbd::Image* image,
+                                const librbd::snap_info_t &snap_info)
+{
+  librbd::snap_namespace_type_t namespace_type;
+  int r = image->snap_get_namespace_type(snap_info.id, &namespace_type);
+  if (r < 0) {
+    return false;
+  }
+  return namespace_type != RBD_SNAP_NAMESPACE_TYPE_USER;
+}
+
+void get_mirror_peer_sites(
+    librados::IoCtx& io_ctx,
+    std::vector<librbd::mirror_peer_site_t>* mirror_peers) {
+  librados::IoCtx default_io_ctx;
+  default_io_ctx.dup(io_ctx);
+  default_io_ctx.set_namespace("");
+
+  mirror_peers->clear();
 
-  if (vm.count(image_key)) {
-    return true;
+  librbd::RBD rbd;
+  int r = rbd.mirror_peer_site_list(default_io_ctx, mirror_peers);
+  if (r < 0 && r != -ENOENT) {
+    std::cerr << "rbd: failed to list mirror peers" << std::endl;
   }
+}
 
-  std::string spec = get_positional_argument(vm, spec_arg_index);
-  if (!spec.empty()) {
-    return true;
+void get_mirror_peer_mirror_uuids_to_names(
+    const std::vector<librbd::mirror_peer_site_t>& mirror_peers,
+    std::map<std::string, std::string>* mirror_uuids_to_name) {
+  mirror_uuids_to_name->clear();
+  for (auto& peer : mirror_peers) {
+    if (!peer.mirror_uuid.empty() && !peer.site_name.empty()) {
+      (*mirror_uuids_to_name)[peer.mirror_uuid] = peer.site_name;
+    }
   }
+}
 
-  return false;
+void populate_unknown_mirror_image_site_statuses(
+    const std::vector<librbd::mirror_peer_site_t>& mirror_peers,
+    librbd::mirror_image_global_status_t* global_status) {
+  std::set<std::string> missing_mirror_uuids;
+  librbd::mirror_peer_direction_t mirror_peer_direction =
+    RBD_MIRROR_PEER_DIRECTION_RX_TX;
+  for (auto& peer : mirror_peers) {
+    if (peer.uuid == mirror_peers.begin()->uuid) {
+      mirror_peer_direction = peer.direction;
+    } else if (mirror_peer_direction != RBD_MIRROR_PEER_DIRECTION_RX_TX &&
+               mirror_peer_direction != peer.direction) {
+      mirror_peer_direction = RBD_MIRROR_PEER_DIRECTION_RX_TX;
+    }
+
+    if (!peer.mirror_uuid.empty() &&
+        peer.direction != RBD_MIRROR_PEER_DIRECTION_TX) {
+      missing_mirror_uuids.insert(peer.mirror_uuid);
+    }
+  }
+
+  if (mirror_peer_direction != RBD_MIRROR_PEER_DIRECTION_TX) {
+    missing_mirror_uuids.insert(RBD_MIRROR_IMAGE_STATUS_LOCAL_MIRROR_UUID);
+  }
+
+  std::vector<librbd::mirror_image_site_status_t> site_statuses;
+  site_statuses.reserve(missing_mirror_uuids.size());
+
+  for (auto& site_status : global_status->site_statuses) {
+    if (missing_mirror_uuids.count(site_status.mirror_uuid) > 0) {
+      missing_mirror_uuids.erase(site_status.mirror_uuid);
+      site_statuses.push_back(site_status);
+    }
+  }
+
+  for (auto& mirror_uuid : missing_mirror_uuids) {
+    site_statuses.push_back({mirror_uuid, MIRROR_IMAGE_STATUS_STATE_UNKNOWN,
+                             "status not found", 0, false});
+  }
+
+  std::swap(global_status->site_statuses, site_statuses);
+}
+
+int mgr_command(librados::Rados& rados, const std::string& cmd,
+                const std::map<std::string, std::string> &args,
+                std::ostream *out_os, std::ostream *err_os) {
+  std::string command = R"(
+    {
+      "prefix": ")" + cmd + R"(", )" + mgr_command_args_to_str(args) + R"(
+    })";
+
+  bufferlist in_bl;
+  bufferlist out_bl;
+  std::string outs;
+  int r = rados.mgr_command(command, in_bl, &out_bl, &outs);
+  if (r < 0) {
+    (*err_os) << "rbd: " << cmd << " failed: " << cpp_strerror(r);
+    if (!outs.empty()) {
+      (*err_os) << ": " << outs;
+    }
+    (*err_os) << std::endl;
+    return r;
+  }
+
+  if (out_bl.length() != 0) {
+    (*out_os) << out_bl.c_str();
+  }
+
+  return 0;
 }
 
 } // namespace utils