From c7f71d14a5d31d14f3556a1f1f2a991b5ea0a75d Mon Sep 17 00:00:00 2001 From: Jason Dillaman Date: Fri, 18 Sep 2015 16:45:32 -0400 Subject: [PATCH] rbd: migrated existing command logic to new namespaces No logic changes were introduced during the migration, only syntax changes were necessary. Signed-off-by: Jason Dillaman --- src/tools/rbd/action/BenchWrite.cc | 189 +++++++++++++++ src/tools/rbd/action/Children.cc | 37 +++ src/tools/rbd/action/Clone.cc | 21 ++ src/tools/rbd/action/Copy.cc | 19 ++ src/tools/rbd/action/Create.cc | 24 ++ src/tools/rbd/action/Diff.cc | 65 +++++ src/tools/rbd/action/DiskUsage.cc | 208 ++++++++++++++++ src/tools/rbd/action/Export.cc | 134 ++++++++++ src/tools/rbd/action/ExportDiff.cc | 187 ++++++++++++++ src/tools/rbd/action/Feature.cc | 6 + src/tools/rbd/action/Flatten.cc | 17 ++ src/tools/rbd/action/ImageMeta.cc | 107 ++++++++ src/tools/rbd/action/Import.cc | 220 +++++++++++++++++ src/tools/rbd/action/ImportDiff.cc | 159 ++++++++++++ src/tools/rbd/action/Info.cc | 171 +++++++++++++ src/tools/rbd/action/Kernel.cc | 237 +++++++++++++++++- src/tools/rbd/action/List.cc | 174 +++++++++++++ src/tools/rbd/action/Lock.cc | 95 ++++++++ src/tools/rbd/action/MergeDiff.cc | 377 +++++++++++++++++++++++++++++ src/tools/rbd/action/ObjectMap.cc | 18 ++ src/tools/rbd/action/Remove.cc | 33 +++ src/tools/rbd/action/Rename.cc | 15 ++ src/tools/rbd/action/Resize.cc | 17 ++ src/tools/rbd/action/Snap.cc | 181 ++++++++++++++ src/tools/rbd/action/Status.cc | 72 ++++++ src/tools/rbd/action/Watch.cc | 54 +++++ 26 files changed, 2836 insertions(+), 1 deletion(-) diff --git a/src/tools/rbd/action/BenchWrite.cc b/src/tools/rbd/action/BenchWrite.cc index 928ce5419f575..d767c087991ca 100644 --- a/src/tools/rbd/action/BenchWrite.cc +++ b/src/tools/rbd/action/BenchWrite.cc @@ -6,7 +6,12 @@ #include "tools/rbd/Utils.h" #include "common/errno.h" #include "common/strtol.h" +#include "common/Cond.h" +#include "common/Mutex.h" #include +#include +#include +#include #include namespace rbd { @@ -50,6 +55,184 @@ void validate(boost::any& v, const std::vector& values, } // anonymous namespace +static void rbd_bencher_completion(void *c, void *pc); +struct rbd_bencher; + +struct rbd_bencher { + librbd::Image *image; + Mutex lock; + Cond cond; + int in_flight; + + rbd_bencher(librbd::Image *i) + : image(i), + lock("rbd_bencher::lock"), + in_flight(0) + { } + + bool start_write(int max, uint64_t off, uint64_t len, bufferlist& bl, + int op_flags) + { + { + Mutex::Locker l(lock); + if (in_flight >= max) + return false; + in_flight++; + } + librbd::RBD::AioCompletion *c = + new librbd::RBD::AioCompletion((void *)this, rbd_bencher_completion); + image->aio_write2(off, len, bl, c, op_flags); + //cout << "start " << c << " at " << off << "~" << len << std::endl; + return true; + } + + void wait_for(int max) { + Mutex::Locker l(lock); + while (in_flight > max) { + utime_t dur; + dur.set_from_double(.2); + cond.WaitInterval(g_ceph_context, lock, dur); + } + } + +}; + +void rbd_bencher_completion(void *vc, void *pc) +{ + librbd::RBD::AioCompletion *c = (librbd::RBD::AioCompletion *)vc; + rbd_bencher *b = static_cast(pc); + //cout << "complete " << c << std::endl; + int ret = c->get_return_value(); + if (ret != 0) { + cout << "write error: " << cpp_strerror(ret) << std::endl; + assert(0 == ret); + } + b->lock.Lock(); + b->in_flight--; + b->cond.Signal(); + b->lock.Unlock(); + c->release(); +} + +int do_bench_write(librbd::Image& image, uint64_t io_size, + uint64_t io_threads, uint64_t io_bytes, + bool random) +{ + rbd_bencher b(&image); + + std::cout << "bench-write " + << " io_size " << io_size + << " io_threads " << io_threads + << " bytes " << io_bytes + << " pattern " << (random ? "random" : "sequential") + << std::endl; + + srand(time(NULL) % (unsigned long) -1); + + bufferptr bp(io_size); + memset(bp.c_str(), rand() & 0xff, io_size); + bufferlist bl; + bl.push_back(bp); + + utime_t start = ceph_clock_now(NULL); + utime_t last; + unsigned ios = 0; + + uint64_t size = 0; + image.size(&size); + + vector thread_offset; + uint64_t i; + uint64_t start_pos; + + // disturb all thread's offset, used by seq write + for (i = 0; i < io_threads; i++) { + start_pos = (rand() % (size / io_size)) * io_size; + thread_offset.push_back(start_pos); + } + + const int WINDOW_SIZE = 5; + typedef boost::accumulators::accumulator_set< + double, boost::accumulators::stats< + boost::accumulators::tag::rolling_sum> > RollingSum; + + RollingSum time_acc( + boost::accumulators::tag::rolling_window::window_size = WINDOW_SIZE); + RollingSum ios_acc( + boost::accumulators::tag::rolling_window::window_size = WINDOW_SIZE); + RollingSum off_acc( + boost::accumulators::tag::rolling_window::window_size = WINDOW_SIZE); + uint64_t cur_ios = 0; + uint64_t cur_off = 0; + + int op_flags; + if (random) { + op_flags = LIBRADOS_OP_FLAG_FADVISE_RANDOM; + } else { + op_flags = LIBRADOS_OP_FLAG_FADVISE_SEQUENTIAL; + } + + printf(" SEC OPS OPS/SEC BYTES/SEC\n"); + uint64_t off; + for (off = 0; off < io_bytes; ) { + b.wait_for(io_threads - 1); + i = 0; + while (i < io_threads && off < io_bytes) { + if (random) { + thread_offset[i] = (rand() % (size / io_size)) * io_size; + } else { + thread_offset[i] += io_size; + if (thread_offset[i] + io_size > size) + thread_offset[i] = 0; + } + + if (!b.start_write(io_threads, thread_offset[i], io_size, bl, op_flags)) + break; + + ++i; + ++ios; + off += io_size; + + ++cur_ios; + cur_off += io_size; + } + + utime_t now = ceph_clock_now(NULL); + utime_t elapsed = now - start; + if (last.is_zero()) { + last = elapsed; + } else if (elapsed.sec() != last.sec()) { + time_acc(elapsed - last); + ios_acc(static_cast(cur_ios)); + off_acc(static_cast(cur_off)); + cur_ios = 0; + cur_off = 0; + + double time_sum = boost::accumulators::rolling_sum(time_acc); + printf("%5d %8d %8.2lf %8.2lf\n", + (int)elapsed, + (int)(ios - io_threads), + boost::accumulators::rolling_sum(ios_acc) / time_sum, + boost::accumulators::rolling_sum(off_acc) / time_sum); + last = elapsed; + } + } + b.wait_for(0); + int r = image.flush(); + if (r < 0) { + std::cerr << "Error flushing data at the end: " << cpp_strerror(r) + << std::endl; + } + + utime_t now = ceph_clock_now(NULL); + double elapsed = now - start; + + printf("elapsed: %5d ops: %8d ops/sec: %8.2lf bytes/sec: %8.2lf\n", + (int)elapsed, ios, (double)ios / elapsed, (double)off / elapsed); + + return 0; +} + void get_arguments(po::options_description *positional, po::options_description *options) { at::add_image_spec_options(positional, options, at::ARGUMENT_MODIFIER_NONE); @@ -110,6 +293,12 @@ int execute(const po::variables_map &vm) { return r; } + r = do_bench_write(image, bench_io_size, bench_io_threads, bench_bytes, + bench_random); + if (r < 0) { + std::cerr << "bench-write failed: " << cpp_strerror(r) << std::endl; + return r; + } return 0; } diff --git a/src/tools/rbd/action/Children.cc b/src/tools/rbd/action/Children.cc index f368a8717379a..b930eb90bc97b 100644 --- a/src/tools/rbd/action/Children.cc +++ b/src/tools/rbd/action/Children.cc @@ -16,6 +16,37 @@ namespace children { namespace at = argument_types; namespace po = boost::program_options; +int do_list_children(librbd::Image &image, Formatter *f) +{ + std::set > children; + int r; + + r = image.list_children(&children); + if (r < 0) + return r; + + if (f) + f->open_array_section("children"); + + for (auto &child_it : children) { + if (f) { + f->open_object_section("child"); + f->dump_string("pool", child_it.first); + f->dump_string("image", child_it.second); + f->close_section(); + } else { + std::cout << child_it.first << "/" << child_it.second << std::endl; + } + } + + if (f) { + f->close_section(); + f->flush(std::cout); + } + + return 0; +} + void get_arguments(po::options_description *positional, po::options_description *options) { at::add_snap_spec_options(positional, options, at::ARGUMENT_MODIFIER_NONE); @@ -49,6 +80,12 @@ int execute(const po::variables_map &vm) { return r; } + r = do_list_children(image, formatter.get()); + if (r < 0) { + std::cerr << "rbd: listing children failed: " << cpp_strerror(r) + << std::endl; + return r; + } return 0; } diff --git a/src/tools/rbd/action/Clone.cc b/src/tools/rbd/action/Clone.cc index 08b6ddd5a260f..6c98433d7430a 100644 --- a/src/tools/rbd/action/Clone.cc +++ b/src/tools/rbd/action/Clone.cc @@ -15,6 +15,19 @@ namespace clone { namespace at = argument_types; namespace po = boost::program_options; +int do_clone(librbd::RBD &rbd, librados::IoCtx &p_ioctx, + const char *p_name, const char *p_snapname, + librados::IoCtx &c_ioctx, const char *c_name, + uint64_t features, int *c_order, + uint64_t stripe_unit, uint64_t stripe_count) { + if ((features & RBD_FEATURE_LAYERING) != RBD_FEATURE_LAYERING) { + return -EINVAL; + } + + return rbd.clone2(p_ioctx, p_name, p_snapname, c_ioctx, c_name, features, + c_order, stripe_unit, stripe_count); +} + void get_arguments(po::options_description *positional, po::options_description *options) { at::add_snap_spec_options(positional, options, at::ARGUMENT_MODIFIER_SOURCE); @@ -67,6 +80,14 @@ int execute(const po::variables_map &vm) { return r; } + librbd::RBD rbd; + r = do_clone(rbd, io_ctx, image_name.c_str(), snap_name.c_str(), dst_io_ctx, + dst_image_name.c_str(), features, &order, stripe_unit, + stripe_count); + if (r < 0) { + std::cerr << "rbd: clone error: " << cpp_strerror(r) << std::endl; + return r; + } return 0; } diff --git a/src/tools/rbd/action/Copy.cc b/src/tools/rbd/action/Copy.cc index 79369d9255ade..9275e4b33f1e1 100644 --- a/src/tools/rbd/action/Copy.cc +++ b/src/tools/rbd/action/Copy.cc @@ -15,6 +15,19 @@ namespace copy { namespace at = argument_types; namespace po = boost::program_options; +static int do_copy(librbd::Image &src, librados::IoCtx& dest_pp, + const char *destname, bool no_progress) +{ + utils::ProgressContext pc("Image copy", no_progress); + int r = src.copy_with_progress(dest_pp, destname, pc); + if (r < 0){ + pc.fail(); + return r; + } + pc.finish(); + return 0; +} + void get_arguments(po::options_description *positional, po::options_description *options) { at::add_image_or_snap_spec_options(positional, options, @@ -60,6 +73,12 @@ int execute(const po::variables_map &vm) { return r; } + r = do_copy(image, dst_io_ctx, dst_image_name.c_str(), + vm[at::NO_PROGRESS].as()); + if (r < 0) { + std::cerr << "rbd: copy failed: " << cpp_strerror(r) << std::endl; + return r; + } return 0; } diff --git a/src/tools/rbd/action/Create.cc b/src/tools/rbd/action/Create.cc index 52285e2a86794..49eedb6cc2a0d 100644 --- a/src/tools/rbd/action/Create.cc +++ b/src/tools/rbd/action/Create.cc @@ -15,6 +15,23 @@ namespace create { namespace at = argument_types; namespace po = boost::program_options; +static int do_create(librbd::RBD &rbd, librados::IoCtx& io_ctx, + const char *imgname, uint64_t size, int *order, + int format, uint64_t features, + uint64_t stripe_unit, uint64_t stripe_count) { + int r; + if (format == 1) { + r = rbd.create(io_ctx, imgname, size, order); + } else { + r = rbd.create3(io_ctx, imgname, size, features, order, + stripe_unit, stripe_count); + } + if (r < 0) { + return r; + } + return 0; +} + void get_arguments(po::options_description *positional, po::options_description *options) { at::add_image_spec_options(positional, options, at::ARGUMENT_MODIFIER_NONE); @@ -58,6 +75,13 @@ int execute(const po::variables_map &vm) { return r; } + librbd::RBD rbd; + r = do_create(rbd, io_ctx, image_name.c_str(), size, &order, format, features, + stripe_unit, stripe_count); + if (r < 0) { + std::cerr << "rbd: create error: " << cpp_strerror(r) << std::endl; + return r; + } return 0; } diff --git a/src/tools/rbd/action/Diff.cc b/src/tools/rbd/action/Diff.cc index 921e29687effe..cd0aeb28936ab 100644 --- a/src/tools/rbd/action/Diff.cc +++ b/src/tools/rbd/action/Diff.cc @@ -6,6 +6,7 @@ #include "tools/rbd/Utils.h" #include "common/errno.h" #include "common/Formatter.h" +#include "common/TextTable.h" #include #include @@ -16,6 +17,64 @@ namespace diff { namespace at = argument_types; namespace po = boost::program_options; +struct output_method { + output_method() : f(NULL), t(NULL), empty(true) {} + Formatter *f; + TextTable *t; + bool empty; +}; + +static int diff_cb(uint64_t ofs, size_t len, int exists, void *arg) +{ + output_method *om = static_cast(arg); + om->empty = false; + if (om->f) { + om->f->open_object_section("extent"); + om->f->dump_unsigned("offset", ofs); + om->f->dump_unsigned("length", len); + om->f->dump_string("exists", exists ? "true" : "false"); + om->f->close_section(); + } else { + assert(om->t); + *(om->t) << ofs << len << (exists ? "data" : "zero") << TextTable::endrow; + } + return 0; +} + +static int do_diff(librbd::Image& image, const char *fromsnapname, + bool whole_object, Formatter *f) +{ + int r; + librbd::image_info_t info; + + r = image.stat(info, sizeof(info)); + if (r < 0) + return r; + + output_method om; + if (f) { + om.f = f; + f->open_array_section("extents"); + } else { + om.t = new TextTable(); + om.t->define_column("Offset", TextTable::LEFT, TextTable::LEFT); + om.t->define_column("Length", TextTable::LEFT, TextTable::LEFT); + om.t->define_column("Type", TextTable::LEFT, TextTable::LEFT); + } + + r = image.diff_iterate2(fromsnapname, 0, info.size, true, whole_object, + diff_cb, &om); + if (f) { + f->close_section(); + f->flush(std::cout); + } else { + if (!om.empty) + std::cout << *om.t; + delete om.t; + } + return r; +} + void get_arguments(po::options_description *positional, po::options_description *options) { at::add_image_or_snap_spec_options(positional, options, @@ -61,6 +120,12 @@ int execute(const po::variables_map &vm) { return r; } + r = do_diff(image, from_snap_name.empty() ? nullptr : from_snap_name.c_str(), + diff_whole_object, formatter.get()); + if (r < 0) { + std::cerr << "rbd: diff error: " << cpp_strerror(r) << std::endl; + return -r; + } return 0; } diff --git a/src/tools/rbd/action/DiskUsage.cc b/src/tools/rbd/action/DiskUsage.cc index b2cd1cf27c2b0..8e59ffeff807c 100644 --- a/src/tools/rbd/action/DiskUsage.cc +++ b/src/tools/rbd/action/DiskUsage.cc @@ -4,8 +4,14 @@ #include "tools/rbd/ArgumentTypes.h" #include "tools/rbd/Shell.h" #include "tools/rbd/Utils.h" +#include "include/types.h" +#include "include/stringify.h" +#include "common/errno.h" #include "common/Formatter.h" +#include "common/TextTable.h" +#include #include +#include #include namespace rbd { @@ -15,6 +21,199 @@ namespace disk_usage { namespace at = argument_types; namespace po = boost::program_options; +static int disk_usage_callback(uint64_t offset, size_t len, int exists, + void *arg) { + uint64_t *used_size = reinterpret_cast(arg); + if (exists) { + (*used_size) += len; + } + return 0; +} + +static int compute_image_disk_usage(const std::string& name, + const std::string& snap_name, + const std::string& from_snap_name, + librbd::Image &image, uint64_t size, + TextTable& tbl, Formatter *f, + uint64_t *used_size) { + const char* from = NULL; + if (!from_snap_name.empty()) { + from = from_snap_name.c_str(); + } + + uint64_t flags; + int r = image.get_flags(&flags); + if (r < 0) { + std::cerr << "rbd: failed to retrieve image flags: " << cpp_strerror(r) + << std::endl; + return r; + } + if ((flags & RBD_FLAG_FAST_DIFF_INVALID) != 0) { + std::cerr << "warning: fast-diff map is invalid for " << name + << (snap_name.empty() ? "" : "@" + snap_name) << ". " + << "operation may be slow." << std::endl; + } + + *used_size = 0; + r = image.diff_iterate2(from, 0, size, false, true, + &disk_usage_callback, used_size); + if (r < 0) { + std::cerr << "rbd: failed to iterate diffs: " << cpp_strerror(r) + << std::endl; + return r; + } + + if (f) { + f->open_object_section("image"); + f->dump_string("name", name); + if (!snap_name.empty()) { + f->dump_string("snapshot", snap_name); + } + f->dump_unsigned("provisioned_size", size); + f->dump_unsigned("used_size" , *used_size); + f->close_section(); + } else { + std::string full_name = name; + if (!snap_name.empty()) { + full_name += "@" + snap_name; + } + tbl << full_name + << stringify(si_t(size)) + << stringify(si_t(*used_size)) + << TextTable::endrow; + } + return 0; +} + +static int do_disk_usage(librbd::RBD &rbd, librados::IoCtx &io_ctx, + const char *imgname, const char *snapname, + Formatter *f) { + std::vector names; + int r = rbd.list(io_ctx, names); + if (r == -ENOENT) { + r = 0; + } else if (r < 0) { + return r; + } + + TextTable tbl; + if (f) { + f->open_object_section("stats"); + f->open_array_section("images"); + } else { + tbl.define_column("NAME", TextTable::LEFT, TextTable::LEFT); + tbl.define_column("PROVISIONED", TextTable::RIGHT, TextTable::RIGHT); + tbl.define_column("USED", TextTable::RIGHT, TextTable::RIGHT); + } + + uint64_t used_size = 0; + uint64_t total_prov = 0; + uint64_t total_used = 0; + std::sort(names.begin(), names.end()); + for (std::vector::const_iterator name = names.begin(); + name != names.end(); ++name) { + if (imgname != NULL && *name != imgname) { + continue; + } + + librbd::Image image; + r = rbd.open_read_only(io_ctx, image, name->c_str(), NULL); + if (r < 0) { + if (r != -ENOENT) { + std::cerr << "rbd: error opening " << *name << ": " << cpp_strerror(r) + << std::endl; + } + continue; + } + + uint64_t features; + int r = image.features(&features); + if (r < 0) { + std::cerr << "rbd: failed to retrieve image features: " << cpp_strerror(r) + << std::endl; + return r; + } + if ((features & RBD_FEATURE_FAST_DIFF) == 0) { + std::cerr << "warning: fast-diff map is not enabled for " << *name << ". " + << "operation may be slow." << std::endl; + } + + librbd::image_info_t info; + if (image.stat(info, sizeof(info)) < 0) { + return -EINVAL; + } + + std::vector snap_list; + r = image.snap_list(snap_list); + if (r < 0) { + std::cerr << "rbd: error opening " << *name << " snapshots: " + << cpp_strerror(r) << std::endl; + continue; + } + + std::string last_snap_name; + std::sort(snap_list.begin(), snap_list.end(), + boost::bind(&librbd::snap_info_t::id, _1) < + boost::bind(&librbd::snap_info_t::id, _2)); + for (std::vector::const_iterator snap = + snap_list.begin(); snap != snap_list.end(); ++snap) { + librbd::Image snap_image; + r = rbd.open_read_only(io_ctx, snap_image, name->c_str(), + snap->name.c_str()); + if (r < 0) { + std::cerr << "rbd: error opening snapshot " << *name << "@" + << snap->name << ": " << cpp_strerror(r) << std::endl; + return r; + } + + if (imgname == NULL || (snapname != NULL && snap->name == snapname)) { + r = compute_image_disk_usage(*name, snap->name, last_snap_name, + snap_image, snap->size, tbl, f, + &used_size); + if (r < 0) { + return r; + } + + if (snapname != NULL) { + total_prov += snap->size; + } + total_used += used_size; + } + last_snap_name = snap->name; + } + + if (snapname == NULL) { + r = compute_image_disk_usage(*name, "", last_snap_name, image, info.size, + tbl, f, &used_size); + if (r < 0) { + return r; + } + total_prov += info.size; + total_used += used_size; + } + } + + if (f) { + f->close_section(); + if (imgname == NULL) { + f->dump_unsigned("total_provisioned_size", total_prov); + f->dump_unsigned("total_used_size", total_used); + } + f->close_section(); + f->flush(std::cout); + } else { + if (imgname == NULL) { + tbl << "" + << stringify(si_t(total_prov)) + << stringify(si_t(total_used)) + << TextTable::endrow; + } + std::cout << tbl; + } + + return 0; +} + void get_arguments(po::options_description *positional, po::options_description *options) { at::add_image_or_snap_spec_options(positional, options, @@ -48,6 +247,15 @@ int execute(const po::variables_map &vm) { return r; } + librbd::RBD rbd; + r = do_disk_usage(rbd, io_ctx, + image_name.empty() ? nullptr: image_name.c_str() , + snap_name.empty() ? nullptr : snap_name.c_str(), + formatter.get()); + if (r < 0) { + std::cerr << "du failed: " << cpp_strerror(r) << std::endl; + return r; + } return 0; } diff --git a/src/tools/rbd/action/Export.cc b/src/tools/rbd/action/Export.cc index 9f3b65d84f5f2..324a4b320dd5f 100644 --- a/src/tools/rbd/action/Export.cc +++ b/src/tools/rbd/action/Export.cc @@ -4,9 +4,12 @@ #include "tools/rbd/ArgumentTypes.h" #include "tools/rbd/Shell.h" #include "tools/rbd/Utils.h" +#include "include/Context.h" #include "common/errno.h" +#include "common/Throttle.h" #include #include +#include namespace rbd { namespace action { @@ -15,6 +18,132 @@ namespace export_full { namespace at = argument_types; namespace po = boost::program_options; +class C_Export : public Context +{ +public: + C_Export(SimpleThrottle &simple_throttle, librbd::Image &image, + uint64_t offset, uint64_t length, int fd) + : m_aio_completion( + new librbd::RBD::AioCompletion(this, &utils::aio_context_callback)), + m_throttle(simple_throttle), m_image(image), m_offset(offset), + m_length(length), m_fd(fd) + { + } + + void send() + { + m_throttle.start_op(); + + int op_flags = LIBRADOS_OP_FLAG_FADVISE_SEQUENTIAL | + LIBRADOS_OP_FLAG_FADVISE_NOCACHE; + int r = m_image.aio_read2(m_offset, m_length, m_bufferlist, + m_aio_completion, op_flags); + if (r < 0) { + cerr << "rbd: error requesting read from source image" << std::endl; + m_aio_completion->release(); + m_throttle.end_op(r); + } + } + + virtual void finish(int r) + { + BOOST_SCOPE_EXIT((&m_throttle) (&r)) + { + m_throttle.end_op(r); + } BOOST_SCOPE_EXIT_END + + if (r < 0) { + cerr << "rbd: error reading from source image at offset " + << m_offset << ": " << cpp_strerror(r) << std::endl; + return; + } + + assert(m_bufferlist.length() == static_cast(r)); + if (m_fd != STDOUT_FILENO) { + if (m_bufferlist.is_zero()) { + return; + } + + uint64_t chkret = lseek64(m_fd, m_offset, SEEK_SET); + if (chkret != m_offset) { + cerr << "rbd: error seeking destination image to offset " + << m_offset << std::endl; + r = -errno; + return; + } + } + + r = m_bufferlist.write_fd(m_fd); + if (r < 0) { + cerr << "rbd: error writing to destination image at offset " + << m_offset << std::endl; + } + } + +private: + librbd::RBD::AioCompletion *m_aio_completion; + SimpleThrottle &m_throttle; + librbd::Image &m_image; + bufferlist m_bufferlist; + uint64_t m_offset; + uint64_t m_length; + int m_fd; +}; + +static int do_export(librbd::Image& image, const char *path, bool no_progress) +{ + librbd::image_info_t info; + int64_t r = image.stat(info, sizeof(info)); + if (r < 0) + return r; + + int fd; + int max_concurrent_ops; + bool to_stdout = (strcmp(path, "-") == 0); + if (to_stdout) { + fd = STDOUT_FILENO; + max_concurrent_ops = 1; + } else { + max_concurrent_ops = max(g_conf->rbd_concurrent_management_ops, 1); + fd = open(path, O_WRONLY | O_CREAT | O_EXCL, 0644); + if (fd < 0) { + return -errno; + } + posix_fadvise(fd, 0, 0, POSIX_FADV_SEQUENTIAL); + } + + utils::ProgressContext pc("Exporting image", no_progress); + + SimpleThrottle throttle(max_concurrent_ops, false); + uint64_t period = image.get_stripe_count() * (1ull << info.order); + for (uint64_t offset = 0; offset < info.size; offset += period) { + if (throttle.pending_error()) { + break; + } + + uint64_t length = min(period, info.size - offset); + C_Export *ctx = new C_Export(throttle, image, offset, length, fd); + ctx->send(); + + pc.update_progress(offset, info.size); + } + + r = throttle.wait_for_ret(); + if (!to_stdout) { + if (r >= 0) { + r = ftruncate(fd, info.size); + } + close(fd); + } + + if (r < 0) { + pc.fail(); + } else { + pc.finish(); + } + return r; +} + void get_arguments(po::options_description *positional, po::options_description *options) { at::add_image_or_snap_spec_options(positional, options, @@ -51,6 +180,11 @@ int execute(const po::variables_map &vm) { return r; } + r = do_export(image, path.c_str(), vm[at::NO_PROGRESS].as()); + if (r < 0) { + std::cerr << "rbd: export error: " << cpp_strerror(r) << std::endl; + return r; + } return 0; } diff --git a/src/tools/rbd/action/ExportDiff.cc b/src/tools/rbd/action/ExportDiff.cc index 2b061ee02e497..245bbf3ab0b4e 100644 --- a/src/tools/rbd/action/ExportDiff.cc +++ b/src/tools/rbd/action/ExportDiff.cc @@ -4,9 +4,14 @@ #include "tools/rbd/ArgumentTypes.h" #include "tools/rbd/Shell.h" #include "tools/rbd/Utils.h" +#include "include/encoding.h" #include "common/errno.h" +#include "common/Throttle.h" +#include #include +#include #include +#include namespace rbd { namespace action { @@ -15,6 +20,179 @@ namespace export_diff { namespace at = argument_types; namespace po = boost::program_options; +struct ExportDiffContext { + librbd::Image *image; + int fd; + uint64_t totalsize; + utils::ProgressContext pc; + OrderedThrottle throttle; + + ExportDiffContext(librbd::Image *i, int f, uint64_t t, int max_ops, + bool no_progress) : + image(i), fd(f), totalsize(t), pc("Exporting image", no_progress), + throttle(max_ops, true) { + } +}; + +class C_ExportDiff : public Context { +public: + C_ExportDiff(ExportDiffContext *edc, uint64_t offset, uint64_t length, + bool exists) + : m_export_diff_context(edc), m_offset(offset), m_length(length), + m_exists(exists) { + } + + int send() { + if (m_export_diff_context->throttle.pending_error()) { + return m_export_diff_context->throttle.wait_for_ret(); + } + + C_OrderedThrottle *ctx = m_export_diff_context->throttle.start_op(this); + if (m_exists) { + librbd::RBD::AioCompletion *aio_completion = + new librbd::RBD::AioCompletion(ctx, &utils::aio_context_callback); + + int op_flags = LIBRADOS_OP_FLAG_FADVISE_NOCACHE; + int r = m_export_diff_context->image->aio_read2( + m_offset, m_length, m_read_data, aio_completion, op_flags); + if (r < 0) { + aio_completion->release(); + ctx->complete(r); + } + } else { + ctx->complete(0); + } + return 0; + } + + static int export_diff_cb(uint64_t offset, size_t length, int exists, + void *arg) { + ExportDiffContext *edc = reinterpret_cast(arg); + + C_ExportDiff *context = new C_ExportDiff(edc, offset, length, exists); + return context->send(); + } + +protected: + virtual void finish(int r) { + if (r >= 0) { + if (m_exists) { + m_exists = !m_read_data.is_zero(); + } + r = write_extent(m_export_diff_context, m_offset, m_length, m_exists); + if (r == 0 && m_exists) { + r = m_read_data.write_fd(m_export_diff_context->fd); + } + } + m_export_diff_context->throttle.end_op(r); + } + +private: + ExportDiffContext *m_export_diff_context; + uint64_t m_offset; + uint64_t m_length; + bool m_exists; + bufferlist m_read_data; + + static int write_extent(ExportDiffContext *edc, uint64_t offset, + uint64_t length, bool exists) { + // extent + bufferlist bl; + __u8 tag = exists ? 'w' : 'z'; + ::encode(tag, bl); + ::encode(offset, bl); + ::encode(length, bl); + int r = bl.write_fd(edc->fd); + + edc->pc.update_progress(offset, edc->totalsize); + return r; + } +}; + +static int do_export_diff(librbd::Image& image, const char *fromsnapname, + const char *endsnapname, bool whole_object, + const char *path, bool no_progress) +{ + int r; + librbd::image_info_t info; + int fd; + + r = image.stat(info, sizeof(info)); + if (r < 0) + return r; + + if (strcmp(path, "-") == 0) + fd = 1; + else + fd = open(path, O_WRONLY | O_CREAT | O_EXCL, 0644); + if (fd < 0) + return -errno; + + BOOST_SCOPE_EXIT((&r) (&fd) (&path)) { + close(fd); + if (r < 0 && fd != 1) { + remove(path); + } + } BOOST_SCOPE_EXIT_END + + { + // header + bufferlist bl; + bl.append(utils::RBD_DIFF_BANNER); + + __u8 tag; + if (fromsnapname) { + tag = 'f'; + ::encode(tag, bl); + std::string from(fromsnapname); + ::encode(from, bl); + } + + if (endsnapname) { + tag = 't'; + ::encode(tag, bl); + std::string to(endsnapname); + ::encode(to, bl); + } + + tag = 's'; + ::encode(tag, bl); + uint64_t endsize = info.size; + ::encode(endsize, bl); + + r = bl.write_fd(fd); + if (r < 0) { + return r; + } + } + ExportDiffContext edc(&image, fd, info.size, + g_conf->rbd_concurrent_management_ops, no_progress); + r = image.diff_iterate2(fromsnapname, 0, info.size, true, whole_object, + &C_ExportDiff::export_diff_cb, (void *)&edc); + if (r < 0) { + goto out; + } + + r = edc.throttle.wait_for_ret(); + if (r < 0) { + goto out; + } + + { + __u8 tag = 'e'; + bufferlist bl; + ::encode(tag, bl); + r = bl.write_fd(fd); + } + + out: + if (r < 0) + edc.pc.fail(); + else + edc.pc.finish(); + return r; +} + void get_arguments(po::options_description *positional, po::options_description *options) { at::add_image_or_snap_spec_options(positional, options, @@ -60,6 +238,15 @@ int execute(const po::variables_map &vm) { return r; } + r = do_export_diff(image, + from_snap_name.empty() ? nullptr : from_snap_name.c_str(), + snap_name.empty() ? nullptr : snap_name.c_str(), + vm[at::WHOLE_OBJECT].as(), path.c_str(), + vm[at::NO_PROGRESS].as()); + if (r < 0) { + std::cerr << "rbd: export-diff error: " << cpp_strerror(r) << std::endl; + return r; + } return 0; } diff --git a/src/tools/rbd/action/Feature.cc b/src/tools/rbd/action/Feature.cc index 2255b82bfd5ef..4bd61a6aa9a81 100644 --- a/src/tools/rbd/action/Feature.cc +++ b/src/tools/rbd/action/Feature.cc @@ -57,6 +57,12 @@ int execute(const po::variables_map &vm, bool enabled) { return r; } + r = image.update_features(boost::any_cast(features_any), enabled); + if (r < 0) { + std::cerr << "rbd: failed to update image features: " << cpp_strerror(r) + << std::endl; + return r; + } return 0; } diff --git a/src/tools/rbd/action/Flatten.cc b/src/tools/rbd/action/Flatten.cc index a8d096c8ffb85..51225437aec38 100644 --- a/src/tools/rbd/action/Flatten.cc +++ b/src/tools/rbd/action/Flatten.cc @@ -15,6 +15,18 @@ namespace flatten { namespace at = argument_types; namespace po = boost::program_options; +static int do_flatten(librbd::Image& image, bool no_progress) +{ + utils::ProgressContext pc("Image flatten", no_progress); + int r = image.flatten_with_progress(pc); + if (r < 0) { + pc.fail(); + return r; + } + pc.finish(); + return 0; +} + void get_arguments(po::options_description *positional, po::options_description *options) { at::add_image_spec_options(positional, options, at::ARGUMENT_MODIFIER_NONE); @@ -42,6 +54,11 @@ int execute(const po::variables_map &vm) { return r; } + r = do_flatten(image, vm[at::NO_PROGRESS].as()); + if (r < 0) { + std::cerr << "rbd: flatten error: " << cpp_strerror(r) << std::endl; + return r; + } return 0; } diff --git a/src/tools/rbd/action/ImageMeta.cc b/src/tools/rbd/action/ImageMeta.cc index 28fc1e5b1a46c..2cf1a25a2ebaa 100644 --- a/src/tools/rbd/action/ImageMeta.cc +++ b/src/tools/rbd/action/ImageMeta.cc @@ -6,6 +6,7 @@ #include "tools/rbd/Utils.h" #include "common/errno.h" #include "common/Formatter.h" +#include "common/TextTable.h" #include #include @@ -34,6 +35,88 @@ int get_key(const po::variables_map &vm, std::string *key) { } // anonymous namespace +static int do_metadata_list(librbd::Image& image, Formatter *f) +{ + std::map pairs; + int r; + TextTable tbl; + + r = image.metadata_list("", 0, &pairs); + if (r < 0) { + std::cerr << "failed to list metadata of image : " << cpp_strerror(r) + << std::endl; + return r; + } + + if (f) { + f->open_object_section("metadatas"); + } else { + tbl.define_column("Key", TextTable::LEFT, TextTable::LEFT); + tbl.define_column("Value", TextTable::LEFT, TextTable::LEFT); + } + + if (!pairs.empty()) { + bool one = (pairs.size() == 1); + + if (!f) { + std::cout << "There " << (one ? "is " : "are ") << pairs.size() + << " metadata" << (one ? "" : "s") << " on this image.\n"; + } + + for (std::map::iterator it = pairs.begin(); + it != pairs.end(); ++it) { + std::string val(it->second.c_str(), it->second.length()); + if (f) { + f->dump_string(it->first.c_str(), val.c_str()); + } else { + tbl << it->first << val.c_str() << TextTable::endrow; + } + } + if (!f) + std::cout << tbl; + } + + if (f) { + f->close_section(); + f->flush(std::cout); + } + return 0; +} + +static int do_metadata_set(librbd::Image& image, const char *key, + const char *value) +{ + int r = image.metadata_set(key, value); + if (r < 0) { + std::cerr << "failed to set metadata " << key << " of image : " + << cpp_strerror(r) << std::endl; + } + return r; +} + +static int do_metadata_remove(librbd::Image& image, const char *key) +{ + int r = image.metadata_remove(key); + if (r < 0) { + std::cerr << "failed to remove metadata " << key << " of image : " + << cpp_strerror(r) << std::endl; + } + return r; +} + +static int do_metadata_get(librbd::Image& image, const char *key) +{ + std::string s; + int r = image.metadata_get(key, &s); + if (r < 0) { + std::cerr << "failed to get metadata " << key << " of image : " + << cpp_strerror(r) << std::endl; + return r; + } + std::cout << s << std::endl; + return r; +} + void get_list_arguments(po::options_description *positional, po::options_description *options) { at::add_image_spec_options(positional, options, at::ARGUMENT_MODIFIER_NONE); @@ -67,6 +150,12 @@ int execute_list(const po::variables_map &vm) { return r; } + r = do_metadata_list(image, formatter.get()); + if (r < 0) { + std::cerr << "rbd: listing metadata failed: " << cpp_strerror(r) + << std::endl; + return r; + } return 0; } @@ -103,6 +192,12 @@ int execute_get(const po::variables_map &vm) { return r; } + r = do_metadata_get(image, key.c_str()); + if (r < 0) { + std::cerr << "rbd: getting metadata failed: " << cpp_strerror(r) + << std::endl; + return r; + } return 0; } @@ -147,6 +242,12 @@ int execute_set(const po::variables_map &vm) { return r; } + r = do_metadata_set(image, key.c_str(), value.c_str()); + if (r < 0) { + std::cerr << "rbd: setting metadata failed: " << cpp_strerror(r) + << std::endl; + return r; + } return 0; } @@ -183,6 +284,12 @@ int execute_remove(const po::variables_map &vm) { return r; } + r = do_metadata_remove(image, key.c_str()); + if (r < 0) { + std::cerr << "rbd: removing metadata failed: " << cpp_strerror(r) + << std::endl; + return r; + } return 0; } diff --git a/src/tools/rbd/action/Import.cc b/src/tools/rbd/action/Import.cc index f5dd141fe831d..bb7cb7d86711f 100644 --- a/src/tools/rbd/action/Import.cc +++ b/src/tools/rbd/action/Import.cc @@ -4,9 +4,13 @@ #include "tools/rbd/ArgumentTypes.h" #include "tools/rbd/Shell.h" #include "tools/rbd/Utils.h" +#include "include/Context.h" +#include "common/blkdev.h" #include "common/errno.h" +#include "common/Throttle.h" #include #include +#include namespace rbd { namespace action { @@ -15,6 +19,213 @@ namespace import { namespace at = argument_types; namespace po = boost::program_options; +class C_Import : public Context { +public: + C_Import(SimpleThrottle &simple_throttle, librbd::Image &image, + bufferlist &bl, uint64_t offset) + : m_throttle(simple_throttle), m_image(image), + m_aio_completion( + new librbd::RBD::AioCompletion(this, &utils::aio_context_callback)), + m_bufferlist(bl), m_offset(offset) + { + } + + void send() + { + m_throttle.start_op(); + + int op_flags = LIBRADOS_OP_FLAG_FADVISE_SEQUENTIAL | + LIBRADOS_OP_FLAG_FADVISE_NOCACHE; + int r = m_image.aio_write2(m_offset, m_bufferlist.length(), m_bufferlist, + m_aio_completion, op_flags); + if (r < 0) { + std::cerr << "rbd: error requesting write to destination image" + << std::endl; + m_aio_completion->release(); + m_throttle.end_op(r); + } + } + + virtual void finish(int r) + { + if (r < 0) { + std::cerr << "rbd: error writing to destination image at offset " + << m_offset << ": " << cpp_strerror(r) << std::endl; + } + m_throttle.end_op(r); + } + +private: + SimpleThrottle &m_throttle; + librbd::Image &m_image; + librbd::RBD::AioCompletion *m_aio_completion; + bufferlist m_bufferlist; + uint64_t m_offset; +}; + +static int do_import(librbd::RBD &rbd, librados::IoCtx& io_ctx, + const char *imgname, int *order, const char *path, + int format, uint64_t features, + uint64_t stripe_unit, uint64_t stripe_count, + bool no_progress) +{ + int fd, r; + struct stat stat_buf; + utils::ProgressContext pc("Importing image", no_progress); + + assert(imgname); + + // default order as usual + if (*order == 0) + *order = 22; + + // try to fill whole imgblklen blocks for sparsification + uint64_t image_pos = 0; + size_t imgblklen = 1 << *order; + char *p = new char[imgblklen]; + size_t reqlen = imgblklen; // amount requested from read + ssize_t readlen; // amount received from one read + size_t blklen = 0; // amount accumulated from reads to fill blk + librbd::Image image; + uint64_t size = 0; + + boost::scoped_ptr throttle; + bool from_stdin = !strcmp(path, "-"); + if (from_stdin) { + throttle.reset(new SimpleThrottle(1, false)); + fd = 0; + size = 1ULL << *order; + } else { + throttle.reset(new SimpleThrottle( + max(g_conf->rbd_concurrent_management_ops, 1), false)); + if ((fd = open(path, O_RDONLY)) < 0) { + r = -errno; + std::cerr << "rbd: error opening " << path << std::endl; + goto done2; + } + + if ((fstat(fd, &stat_buf)) < 0) { + r = -errno; + std::cerr << "rbd: stat error " << path << std::endl; + goto done; + } + if (S_ISDIR(stat_buf.st_mode)) { + r = -EISDIR; + std::cerr << "rbd: cannot import a directory" << std::endl; + goto done; + } + if (stat_buf.st_size) + size = (uint64_t)stat_buf.st_size; + + if (!size) { + int64_t bdev_size = 0; + r = get_block_device_size(fd, &bdev_size); + if (r < 0) { + std::cerr << "rbd: unable to get size of file/block device" + << std::endl; + goto done; + } + assert(bdev_size >= 0); + size = (uint64_t) bdev_size; + } + + posix_fadvise(fd, 0, 0, POSIX_FADV_SEQUENTIAL); + } + + if (format == 1) { + // weird striping not allowed with format 1! + if ((stripe_unit || stripe_count) && + (stripe_unit != (1ull << *order) && stripe_count != 1)) { + std::cerr << "non-default striping not allowed with format 1; " + << "use --image-format 2" << std::endl; + return -EINVAL; + } + r = rbd.create(io_ctx, imgname, size, order); + } else { + r = rbd.create3(io_ctx, imgname, size, features, order, + stripe_unit, stripe_count); + } + if (r < 0) { + std::cerr << "rbd: image creation failed" << std::endl; + goto done; + } + + r = rbd.open(io_ctx, image, imgname); + if (r < 0) { + std::cerr << "rbd: failed to open image" << std::endl; + goto done; + } + + // loop body handles 0 return, as we may have a block to flush + while ((readlen = ::read(fd, p + blklen, reqlen)) >= 0) { + if (throttle->pending_error()) { + break; + } + + blklen += readlen; + // if read was short, try again to fill the block before writing + if (readlen && ((size_t)readlen < reqlen)) { + reqlen -= readlen; + continue; + } + if (!from_stdin) + pc.update_progress(image_pos, size); + + bufferlist bl(blklen); + bl.append(p, blklen); + // resize output image by binary expansion as we go for stdin + if (from_stdin && (image_pos + (size_t)blklen) > size) { + size *= 2; + r = image.resize(size); + if (r < 0) { + std::cerr << "rbd: can't resize image during import" << std::endl; + goto done; + } + } + + // write as much as we got; perhaps less than imgblklen + // but skip writing zeros to create sparse images + if (!bl.is_zero()) { + C_Import *ctx = new C_Import(*throttle, image, bl, image_pos); + ctx->send(); + } + + // done with whole block, whether written or not + image_pos += blklen; + // if read had returned 0, we're at EOF and should quit + if (readlen == 0) + break; + blklen = 0; + reqlen = imgblklen; + } + r = throttle->wait_for_ret(); + if (r < 0) { + goto done; + } + + if (from_stdin) { + r = image.resize(image_pos); + if (r < 0) { + std::cerr << "rbd: final image resize failed" << std::endl; + goto done; + } + } + + r = image.close(); + + done: + if (!from_stdin) { + if (r < 0) + pc.fail(); + else + pc.finish(); + close(fd); + } + done2: + delete[] p; + return r; +} + void get_arguments(po::options_description *positional, po::options_description *options) { at::add_path_options(positional, options, @@ -87,6 +298,15 @@ int execute(const po::variables_map &vm) { return r; } + librbd::RBD rbd; + r = do_import(rbd, io_ctx, image_name.c_str(), &order, path.c_str(), + format, features, stripe_unit, stripe_count, + vm[at::NO_PROGRESS].as()); + if (r < 0) { + std::cerr << "rbd: import failed: " << cpp_strerror(r) << std::endl; + return r; + } + return 0; } diff --git a/src/tools/rbd/action/ImportDiff.cc b/src/tools/rbd/action/ImportDiff.cc index 82e0a83ce4872..9f600f8285d8d 100644 --- a/src/tools/rbd/action/ImportDiff.cc +++ b/src/tools/rbd/action/ImportDiff.cc @@ -4,7 +4,10 @@ #include "tools/rbd/ArgumentTypes.h" #include "tools/rbd/Shell.h" #include "tools/rbd/Utils.h" +#include "include/encoding.h" +#include "common/debug.h" #include "common/errno.h" +#include "common/safe_io.h" #include #include @@ -17,6 +20,157 @@ namespace import_diff { namespace at = argument_types; namespace po = boost::program_options; +static int do_import_diff(librbd::Image &image, const char *path, + bool no_progress) +{ + int fd, r; + struct stat stat_buf; + utils::ProgressContext pc("Importing image diff", no_progress); + uint64_t size = 0; + uint64_t off = 0; + string from, to; + char buf[utils::RBD_DIFF_BANNER.size() + 1]; + + bool from_stdin = !strcmp(path, "-"); + if (from_stdin) { + fd = 0; + } else { + fd = open(path, O_RDONLY); + if (fd < 0) { + r = -errno; + std::cerr << "rbd: error opening " << path << std::endl; + return r; + } + r = ::fstat(fd, &stat_buf); + if (r < 0) + goto done; + size = (uint64_t)stat_buf.st_size; + } + + r = safe_read_exact(fd, buf, utils::RBD_DIFF_BANNER.size()); + if (r < 0) + goto done; + buf[utils::RBD_DIFF_BANNER.size()] = '\0'; + if (strcmp(buf, utils::RBD_DIFF_BANNER.c_str())) { + std::cerr << "invalid banner '" << buf << "', expected '" + << utils::RBD_DIFF_BANNER << "'" << std::endl; + r = -EINVAL; + goto done; + } + + while (true) { + __u8 tag; + r = safe_read_exact(fd, &tag, 1); + if (r < 0) { + goto done; + } + + if (tag == 'e') { + dout(2) << " end diff" << dendl; + break; + } else if (tag == 'f') { + r = utils::read_string(fd, 4096, &from); // 4k limit to make sure we don't get a garbage string + if (r < 0) + goto done; + dout(2) << " from snap " << from << dendl; + + if (!image.snap_exists(from.c_str())) { + std::cerr << "start snapshot '" << from + << "' does not exist in the image, aborting" << std::endl; + r = -EINVAL; + goto done; + } + } + else if (tag == 't') { + r = utils::read_string(fd, 4096, &to); // 4k limit to make sure we don't get a garbage string + if (r < 0) + goto done; + dout(2) << " to snap " << to << dendl; + + // verify this snap isn't already present + if (image.snap_exists(to.c_str())) { + std::cerr << "end snapshot '" << to + << "' already exists, aborting" << std::endl; + r = -EEXIST; + goto done; + } + } else if (tag == 's') { + uint64_t end_size; + char buf[8]; + r = safe_read_exact(fd, buf, 8); + if (r < 0) + goto done; + bufferlist bl; + bl.append(buf, 8); + bufferlist::iterator p = bl.begin(); + ::decode(end_size, p); + uint64_t cur_size; + image.size(&cur_size); + if (cur_size != end_size) { + dout(2) << "resize " << cur_size << " -> " << end_size << dendl; + image.resize(end_size); + } else { + dout(2) << "size " << end_size << " (no change)" << dendl; + } + if (from_stdin) + size = end_size; + } else if (tag == 'w' || tag == 'z') { + uint64_t len; + char buf[16]; + r = safe_read_exact(fd, buf, 16); + if (r < 0) + goto done; + bufferlist bl; + bl.append(buf, 16); + bufferlist::iterator p = bl.begin(); + ::decode(off, p); + ::decode(len, p); + + if (tag == 'w') { + bufferptr bp = buffer::create(len); + r = safe_read_exact(fd, bp.c_str(), len); + if (r < 0) + goto done; + bufferlist data; + data.append(bp); + dout(2) << " write " << off << "~" << len << dendl; + image.write2(off, len, data, LIBRADOS_OP_FLAG_FADVISE_NOCACHE); + } else { + dout(2) << " zero " << off << "~" << len << dendl; + image.discard(off, len); + } + } else { + std::cerr << "unrecognized tag byte " << (int)tag + << " in stream; aborting" << std::endl; + r = -EINVAL; + goto done; + } + if (!from_stdin) { + // progress through input + uint64_t off = lseek64(fd, 0, SEEK_CUR); + pc.update_progress(off, size); + } else if (size) { + // progress through image offsets. this may jitter if blocks + // aren't in order, but it is better than nothing. + pc.update_progress(off, size); + } + } + // take final snap + if (to.length()) { + dout(2) << " create end snap " << to << dendl; + r = image.snap_create(to.c_str()); + } + + done: + if (r < 0) + pc.fail(); + else + pc.finish(); + if (!from_stdin) + close(fd); + return r; +} + void get_arguments(po::options_description *positional, po::options_description *options) { at::add_path_options(positional, options, @@ -52,6 +206,11 @@ int execute(const po::variables_map &vm) { return r; } + r = do_import_diff(image, path.c_str(), vm[at::NO_PROGRESS].as()); + if (r < 0) { + cerr << "rbd: import-diff failed: " << cpp_strerror(r) << std::endl; + return r; + } return 0; } diff --git a/src/tools/rbd/action/Info.cc b/src/tools/rbd/action/Info.cc index 023951f549d34..76e394049f415 100644 --- a/src/tools/rbd/action/Info.cc +++ b/src/tools/rbd/action/Info.cc @@ -4,6 +4,8 @@ #include "tools/rbd/ArgumentTypes.h" #include "tools/rbd/Shell.h" #include "tools/rbd/Utils.h" +#include "include/types.h" +#include "common/errno.h" #include "common/Formatter.h" #include #include @@ -15,6 +17,168 @@ namespace info { namespace at = argument_types; namespace po = boost::program_options; +static void format_bitmask(Formatter *f, const std::string &name, + const std::map& mapping, + uint64_t bitmask) +{ + int count = 0; + std::string group_name(name + "s"); + if (f == NULL) { + std::cout << "\t" << group_name << ": "; + } else { + f->open_array_section(group_name.c_str()); + } + for (std::map::const_iterator it = mapping.begin(); + it != mapping.end(); ++it) { + if ((it->first & bitmask) == 0) { + continue; + } + + if (f == NULL) { + if (count++ > 0) { + std::cout << ", "; + } + std::cout << it->second; + } else { + f->dump_string(name.c_str(), it->second); + } + } + if (f == NULL) { + std::cout << std::endl; + } else { + f->close_section(); + } +} + +static void format_features(Formatter *f, uint64_t features) +{ + format_bitmask(f, "feature", at::ImageFeatures::FEATURE_MAPPING, features); +} + +static void format_flags(Formatter *f, uint64_t flags) +{ + std::map mapping = { + {RBD_FLAG_OBJECT_MAP_INVALID, "object map invalid"}, + {RBD_FLAG_FAST_DIFF_INVALID, "fast diff invalid"}}; + format_bitmask(f, "flag", mapping, flags); +} + +static int do_show_info(const char *imgname, librbd::Image& image, + const char *snapname, Formatter *f) +{ + librbd::image_info_t info; + std::string parent_pool, parent_name, parent_snapname; + uint8_t old_format; + uint64_t overlap, features, flags; + bool snap_protected = false; + int r; + + r = image.stat(info, sizeof(info)); + if (r < 0) + return r; + + r = image.old_format(&old_format); + if (r < 0) + return r; + + r = image.overlap(&overlap); + if (r < 0) + return r; + + r = image.features(&features); + if (r < 0) + return r; + + r = image.get_flags(&flags); + if (r < 0) { + return r; + } + + if (snapname) { + r = image.snap_is_protected(snapname, &snap_protected); + if (r < 0) + return r; + } + + char prefix[RBD_MAX_BLOCK_NAME_SIZE + 1]; + strncpy(prefix, info.block_name_prefix, RBD_MAX_BLOCK_NAME_SIZE); + prefix[RBD_MAX_BLOCK_NAME_SIZE] = '\0'; + + if (f) { + f->open_object_section("image"); + f->dump_string("name", imgname); + f->dump_unsigned("size", info.size); + f->dump_unsigned("objects", info.num_objs); + f->dump_int("order", info.order); + f->dump_unsigned("object_size", info.obj_size); + f->dump_string("block_name_prefix", prefix); + f->dump_int("format", (old_format ? 1 : 2)); + } else { + std::cout << "rbd image '" << imgname << "':\n" + << "\tsize " << prettybyte_t(info.size) << " in " + << info.num_objs << " objects" + << std::endl + << "\torder " << info.order + << " (" << prettybyte_t(info.obj_size) << " objects)" + << std::endl + << "\tblock_name_prefix: " << prefix + << std::endl + << "\tformat: " << (old_format ? "1" : "2") + << std::endl; + } + + if (!old_format) { + format_features(f, features); + format_flags(f, flags); + } + + // snapshot info, if present + if (snapname) { + if (f) { + f->dump_string("protected", snap_protected ? "true" : "false"); + } else { + std::cout << "\tprotected: " << (snap_protected ? "True" : "False") + << std::endl; + } + } + + // parent info, if present + if ((image.parent_info(&parent_pool, &parent_name, &parent_snapname) == 0) && + parent_name.length() > 0) { + if (f) { + f->open_object_section("parent"); + f->dump_string("pool", parent_pool); + f->dump_string("image", parent_name); + f->dump_string("snapshot", parent_snapname); + f->dump_unsigned("overlap", overlap); + f->close_section(); + } else { + std::cout << "\tparent: " << parent_pool << "/" << parent_name + << "@" << parent_snapname << std::endl; + std::cout << "\toverlap: " << prettybyte_t(overlap) << std::endl; + } + } + + // striping info, if feature is set + if (features & RBD_FEATURE_STRIPINGV2) { + if (f) { + f->dump_unsigned("stripe_unit", image.get_stripe_unit()); + f->dump_unsigned("stripe_count", image.get_stripe_count()); + } else { + std::cout << "\tstripe unit: " << prettybyte_t(image.get_stripe_unit()) + << std::endl + << "\tstripe count: " << image.get_stripe_count() << std::endl; + } + } + + if (f) { + f->close_section(); + f->flush(std::cout); + } + + return 0; +} + void get_arguments(po::options_description *positional, po::options_description *options) { at::add_image_or_snap_spec_options(positional, options, @@ -49,6 +213,13 @@ int execute(const po::variables_map &vm) { return r; } + r = do_show_info(image_name.c_str(), image, + snap_name.empty() ? nullptr : snap_name.c_str(), + formatter.get()); + if (r < 0) { + std::cerr << "rbd: info: " << cpp_strerror(r) << std::endl; + return r; + } return 0; } diff --git a/src/tools/rbd/action/Kernel.cc b/src/tools/rbd/action/Kernel.cc index 846b5f594ca89..5daa925bf3c5f 100644 --- a/src/tools/rbd/action/Kernel.cc +++ b/src/tools/rbd/action/Kernel.cc @@ -4,12 +4,19 @@ #include "tools/rbd/ArgumentTypes.h" #include "tools/rbd/Shell.h" #include "tools/rbd/Utils.h" +#include "include/krbd.h" +#include "include/stringify.h" +#include "include/uuid.h" +#include "common/config.h" #include "common/errno.h" +#include "common/strtol.h" #include "common/Formatter.h" +#include "msg/msg_types.h" +#include "global/global_context.h" #include #include -#include #include +#include namespace rbd { namespace action { @@ -18,6 +25,189 @@ namespace kernel { namespace at = argument_types; namespace po = boost::program_options; +namespace { + +std::map map_options; + +} // anonymous namespace + +static std::string map_option_uuid_cb(const char *value_char) +{ + uuid_d u; + if (!u.parse(value_char)) + return ""; + + return stringify(u); +} + +static std::string map_option_ip_cb(const char *value_char) +{ + entity_addr_t a; + const char *endptr; + if (!a.parse(value_char, &endptr) || + endptr != value_char + strlen(value_char)) { + return ""; + } + + return stringify(a.addr); +} + +static std::string map_option_int_cb(const char *value_char) +{ + std::string err; + int d = strict_strtol(value_char, 10, &err); + if (!err.empty() || d < 0) + return ""; + + return stringify(d); +} + +static void put_map_option(const std::string key, std::string val) +{ + map_options[key] = val; +} + +static int put_map_option_value(const std::string opt, const char *value_char, + std::string (*parse_cb)(const char *)) +{ + if (!value_char || *value_char == '\0') { + std::cerr << "rbd: " << opt << " option requires a value" << std::endl; + return -EINVAL; + } + + std::string value = parse_cb(value_char); + if (value.empty()) { + std::cerr << "rbd: invalid " << opt << " value '" << value_char << "'" + << std::endl; + return -EINVAL; + } + + put_map_option(opt, opt + "=" + value); + return 0; +} + +static int parse_map_options(char *options) +{ + for (char *this_char = strtok(options, ", "); + this_char != NULL; + this_char = strtok(NULL, ",")) { + char *value_char; + + if ((value_char = strchr(this_char, '=')) != NULL) + *value_char++ = '\0'; + + if (!strcmp(this_char, "fsid")) { + if (put_map_option_value("fsid", value_char, map_option_uuid_cb)) + return -EINVAL; + } else if (!strcmp(this_char, "ip")) { + if (put_map_option_value("ip", value_char, map_option_ip_cb)) + return -EINVAL; + } else if (!strcmp(this_char, "share") || !strcmp(this_char, "noshare")) { + put_map_option("share", this_char); + } else if (!strcmp(this_char, "crc") || !strcmp(this_char, "nocrc")) { + put_map_option("crc", this_char); + } else if (!strcmp(this_char, "cephx_require_signatures") || + !strcmp(this_char, "nocephx_require_signatures")) { + put_map_option("cephx_require_signatures", this_char); + } else if (!strcmp(this_char, "tcp_nodelay") || + !strcmp(this_char, "notcp_nodelay")) { + put_map_option("tcp_nodelay", this_char); + } else if (!strcmp(this_char, "mount_timeout")) { + if (put_map_option_value("mount_timeout", value_char, map_option_int_cb)) + return -EINVAL; + } else if (!strcmp(this_char, "osdkeepalive")) { + if (put_map_option_value("osdkeepalive", value_char, map_option_int_cb)) + return -EINVAL; + } else if (!strcmp(this_char, "osd_idle_ttl")) { + if (put_map_option_value("osd_idle_ttl", value_char, map_option_int_cb)) + return -EINVAL; + } else if (!strcmp(this_char, "rw") || !strcmp(this_char, "ro")) { + put_map_option("rw", this_char); + } else if (!strcmp(this_char, "queue_depth")) { + if (put_map_option_value("queue_depth", value_char, map_option_int_cb)) + return -EINVAL; + } else { + std::cerr << "rbd: unknown map option '" << this_char << "'" << std::endl; + return -EINVAL; + } + } + + return 0; +} + +static int do_kernel_showmapped(Formatter *f) +{ + struct krbd_ctx *krbd; + int r; + + r = krbd_create_from_context(g_ceph_context, &krbd); + if (r < 0) + return r; + + r = krbd_showmapped(krbd, f); + + krbd_destroy(krbd); + return r; +} + +static int do_kernel_map(const char *poolname, const char *imgname, + const char *snapname) +{ + struct krbd_ctx *krbd; + std::ostringstream oss; + char *devnode; + int r; + + r = krbd_create_from_context(g_ceph_context, &krbd); + if (r < 0) + return r; + + for (std::map::iterator it = map_options.begin(); + it != map_options.end(); ) { + // for compatibility with < 3.7 kernels, assume that rw is on by + // default and omit it even if it was specified by the user + // (see ceph.git commit fb0f1986449b) + if (it->first == "rw" && it->second == "rw") { + map_options.erase(it); + } else { + if (it != map_options.begin()) + oss << ","; + oss << it->second; + ++it; + } + } + + r = krbd_map(krbd, poolname, imgname, snapname, oss.str().c_str(), &devnode); + if (r < 0) + goto out; + + std::cout << devnode << std::endl; + + free(devnode); +out: + krbd_destroy(krbd); + return r; +} + +static int do_kernel_unmap(const char *dev, const char *poolname, + const char *imgname, const char *snapname) +{ + struct krbd_ctx *krbd; + int r; + + r = krbd_create_from_context(g_ceph_context, &krbd); + if (r < 0) + return r; + + if (dev) + r = krbd_unmap(krbd, dev); + else + r = krbd_unmap_by_spec(krbd, poolname, imgname, snapname); + + krbd_destroy(krbd); + return r; +} + void get_show_arguments(po::options_description *positional, po::options_description *options) { at::add_format_options(options); @@ -32,6 +222,11 @@ int execute_show(const po::variables_map &vm) { utils::init_context(); + r = do_kernel_showmapped(formatter.get()); + if (r < 0) { + std::cerr << "rbd: showmapped failed: " << cpp_strerror(r) << std::endl; + return r; + } return 0; } @@ -56,8 +251,41 @@ int execute_map(const po::variables_map &vm) { return r; } + if (vm["read-only"].as()) { + put_map_option("rw", "ro"); + } + + // parse default options first so they can be overwritten by cli options + char *default_map_options = strdup(g_conf->rbd_default_map_options.c_str()); + BOOST_SCOPE_EXIT( (default_map_options) ) { + free(default_map_options); + } BOOST_SCOPE_EXIT_END; + + if (parse_map_options(default_map_options)) { + std::cerr << "rbd: couldn't parse default map options" << std::endl; + return -EINVAL; + } + + if (vm.count("options")) { + char *cli_map_options = strdup(vm["options"].as().c_str()); + BOOST_SCOPE_EXIT( (cli_map_options) ) { + free(cli_map_options); + } BOOST_SCOPE_EXIT_END; + + if (parse_map_options(cli_map_options)) { + std::cerr << "rbd: couldn't parse map options" << std::endl; + return -EINVAL; + } + } + utils::init_context(); + r = do_kernel_map(pool_name.c_str(), image_name.c_str(), snap_name.c_str()); + if (r < 0) { + std::cerr << "rbd: map failed: " << cpp_strerror(r) << std::endl; + return r; + } + return 0; } @@ -101,6 +329,13 @@ int execute_unmap(const po::variables_map &vm) { utils::init_context(); + r = do_kernel_unmap(device_name.empty() ? nullptr : device_name.c_str(), + pool_name.c_str(), image_name.c_str(), + snap_name.empty() ? nullptr : snap_name.c_str()); + if (r < 0) { + std::cerr << "rbd: unmap failed: " << cpp_strerror(r) << std::endl; + return r; + } return 0; } diff --git a/src/tools/rbd/action/List.cc b/src/tools/rbd/action/List.cc index f176d7b7c7374..6b2041ac204a9 100644 --- a/src/tools/rbd/action/List.cc +++ b/src/tools/rbd/action/List.cc @@ -4,8 +4,11 @@ #include "tools/rbd/ArgumentTypes.h" #include "tools/rbd/Shell.h" #include "tools/rbd/Utils.h" +#include "include/stringify.h" +#include "include/types.h" #include "common/errno.h" #include "common/Formatter.h" +#include "common/TextTable.h" #include #include @@ -16,6 +19,170 @@ namespace list { namespace at = argument_types; namespace po = boost::program_options; +int do_list(librbd::RBD &rbd, librados::IoCtx& io_ctx, bool lflag, + Formatter *f) { + std::vector names; + int r = rbd.list(io_ctx, names); + if (r == -ENOENT) + r = 0; + if (r < 0) + return r; + + if (!lflag) { + if (f) + f->open_array_section("images"); + for (std::vector::const_iterator i = names.begin(); + i != names.end(); ++i) { + if (f) + f->dump_string("name", *i); + else + std::cout << *i << std::endl; + } + if (f) { + f->close_section(); + f->flush(std::cout); + } + return 0; + } + + TextTable tbl; + + if (f) { + f->open_array_section("images"); + } else { + tbl.define_column("NAME", TextTable::LEFT, TextTable::LEFT); + tbl.define_column("SIZE", TextTable::RIGHT, TextTable::RIGHT); + tbl.define_column("PARENT", TextTable::LEFT, TextTable::LEFT); + tbl.define_column("FMT", TextTable::RIGHT, TextTable::RIGHT); + tbl.define_column("PROT", TextTable::LEFT, TextTable::LEFT); + tbl.define_column("LOCK", TextTable::LEFT, TextTable::LEFT); + } + + std::string pool, image, snap, parent; + + for (std::vector::const_iterator i = names.begin(); + i != names.end(); ++i) { + librbd::image_info_t info; + librbd::Image im; + + r = rbd.open_read_only(io_ctx, im, i->c_str(), NULL); + // image might disappear between rbd.list() and rbd.open(); ignore + // that, warn about other possible errors (EPERM, say, for opening + // an old-format image, because you need execute permission for the + // class method) + if (r < 0) { + if (r != -ENOENT) { + std::cerr << "rbd: error opening " << *i << ": " << cpp_strerror(r) + << std::endl; + } + // in any event, continue to next image + continue; + } + + // handle second-nth trips through loop + parent.clear(); + r = im.parent_info(&pool, &image, &snap); + if (r < 0 && r != -ENOENT) + return r; + bool has_parent = false; + if (r != -ENOENT) { + parent = pool + "/" + image + "@" + snap; + has_parent = true; + } + + if (im.stat(info, sizeof(info)) < 0) + return -EINVAL; + + uint8_t old_format; + im.old_format(&old_format); + + std::list lockers; + bool exclusive; + r = im.list_lockers(&lockers, &exclusive, NULL); + if (r < 0) + return r; + std::string lockstr; + if (!lockers.empty()) { + lockstr = (exclusive) ? "excl" : "shr"; + } + + if (f) { + f->open_object_section("image"); + f->dump_string("image", *i); + f->dump_unsigned("size", info.size); + if (has_parent) { + f->open_object_section("parent"); + f->dump_string("pool", pool); + f->dump_string("image", image); + f->dump_string("snapshot", snap); + f->close_section(); + } + f->dump_int("format", old_format ? 1 : 2); + if (!lockers.empty()) + f->dump_string("lock_type", exclusive ? "exclusive" : "shared"); + f->close_section(); + } else { + tbl << *i + << stringify(si_t(info.size)) + << parent + << ((old_format) ? '1' : '2') + << "" // protect doesn't apply to images + << lockstr + << TextTable::endrow; + } + + std::vector snaplist; + if (im.snap_list(snaplist) >= 0 && !snaplist.empty()) { + for (std::vector::iterator s = snaplist.begin(); + s != snaplist.end(); ++s) { + bool is_protected; + bool has_parent = false; + parent.clear(); + im.snap_set(s->name.c_str()); + r = im.snap_is_protected(s->name.c_str(), &is_protected); + if (r < 0) + return r; + if (im.parent_info(&pool, &image, &snap) >= 0) { + parent = pool + "/" + image + "@" + snap; + has_parent = true; + } + if (f) { + f->open_object_section("snapshot"); + f->dump_string("image", *i); + f->dump_string("snapshot", s->name); + f->dump_unsigned("size", s->size); + if (has_parent) { + f->open_object_section("parent"); + f->dump_string("pool", pool); + f->dump_string("image", image); + f->dump_string("snapshot", snap); + f->close_section(); + } + f->dump_int("format", old_format ? 1 : 2); + f->dump_string("protected", is_protected ? "true" : "false"); + f->close_section(); + } else { + tbl << *i + "@" + s->name + << stringify(si_t(s->size)) + << parent + << ((old_format) ? '1' : '2') + << (is_protected ? "yes" : "") + << "" // locks don't apply to snaps + << TextTable::endrow; + } + } + } + } + if (f) { + f->close_section(); + f->flush(std::cout); + } else if (!names.empty()) { + std::cout << tbl; + } + + return 0; +} + void get_arguments(po::options_description *positional, po::options_description *options) { positional->add_options() @@ -49,6 +216,13 @@ int execute(const po::variables_map &vm) { return r; } + librbd::RBD rbd; + r = do_list(rbd, io_ctx, vm["long"].as(), formatter.get()); + if (r < 0) { + std::cerr << "rbd: list: " << cpp_strerror(r) << std::endl; + return r; + } + return 0; } diff --git a/src/tools/rbd/action/Lock.cc b/src/tools/rbd/action/Lock.cc index ac0ae4010fca1..2b5eb29ea23fe 100644 --- a/src/tools/rbd/action/Lock.cc +++ b/src/tools/rbd/action/Lock.cc @@ -4,7 +4,9 @@ #include "tools/rbd/ArgumentTypes.h" #include "tools/rbd/Shell.h" #include "tools/rbd/Utils.h" +#include "common/errno.h" #include "common/Formatter.h" +#include "common/TextTable.h" #include #include @@ -33,6 +35,74 @@ int get_id(const po::variables_map &vm, std::string *id) { } // anonymous namespace +static int do_lock_list(librbd::Image& image, Formatter *f) +{ + std::list lockers; + bool exclusive; + std::string tag; + TextTable tbl; + int r; + + r = image.list_lockers(&lockers, &exclusive, &tag); + if (r < 0) + return r; + + if (f) { + f->open_object_section("locks"); + } else { + tbl.define_column("Locker", TextTable::LEFT, TextTable::LEFT); + tbl.define_column("ID", TextTable::LEFT, TextTable::LEFT); + tbl.define_column("Address", TextTable::LEFT, TextTable::LEFT); + } + + if (lockers.size()) { + bool one = (lockers.size() == 1); + + if (!f) { + std::cout << "There " << (one ? "is " : "are ") << lockers.size() + << (exclusive ? " exclusive" : " shared") + << " lock" << (one ? "" : "s") << " on this image.\n"; + if (!exclusive) + std::cout << "Lock tag: " << tag << "\n"; + } + + for (std::list::const_iterator it = lockers.begin(); + it != lockers.end(); ++it) { + if (f) { + f->open_object_section(it->cookie.c_str()); + f->dump_string("locker", it->client); + f->dump_string("address", it->address); + f->close_section(); + } else { + tbl << it->client << it->cookie << it->address << TextTable::endrow; + } + } + if (!f) + std::cout << tbl; + } + + if (f) { + f->close_section(); + f->flush(std::cout); + } + return 0; +} + +static int do_lock_add(librbd::Image& image, const char *cookie, + const char *tag) +{ + if (tag) + return image.lock_shared(cookie, tag); + else + return image.lock_exclusive(cookie); +} + +static int do_lock_remove(librbd::Image& image, const char *client, + const char *cookie) +{ + return image.break_lock(client, cookie); +} + void get_list_arguments(po::options_description *positional, po::options_description *options) { at::add_image_spec_options(positional, options, at::ARGUMENT_MODIFIER_NONE); @@ -66,6 +136,11 @@ int execute_list(const po::variables_map &vm) { return r; } + r = do_lock_list(image, formatter.get()); + if (r < 0) { + std::cerr << "rbd: listing locks failed: " << cpp_strerror(r) << std::endl; + return r; + } return 0; } @@ -109,6 +184,21 @@ int execute_add(const po::variables_map &vm) { return r; } + r = do_lock_add(image, lock_cookie.c_str(), + lock_tag.empty() ? nullptr : lock_tag.c_str()); + if (r < 0) { + if (r == -EBUSY || r == -EEXIST) { + if (!lock_tag.empty()) { + std::cerr << "rbd: lock is alrady held by someone else" + << " with a different tag" << std::endl; + } else { + std::cerr << "rbd: lock is already held by someone else" << std::endl; + } + } else { + std::cerr << "rbd: taking lock failed: " << cpp_strerror(r) << std::endl; + } + return r; + } return 0; } @@ -153,6 +243,11 @@ int execute_remove(const po::variables_map &vm) { return r; } + r = do_lock_remove(image, lock_client.c_str(), lock_cookie.c_str()); + if (r < 0) { + std::cerr << "rbd: releasing lock failed: " << cpp_strerror(r) << std::endl; + return r; + } return 0; } diff --git a/src/tools/rbd/action/MergeDiff.cc b/src/tools/rbd/action/MergeDiff.cc index b06276f7bc883..9e08a3729f09d 100644 --- a/src/tools/rbd/action/MergeDiff.cc +++ b/src/tools/rbd/action/MergeDiff.cc @@ -4,6 +4,8 @@ #include "tools/rbd/ArgumentTypes.h" #include "tools/rbd/Shell.h" #include "tools/rbd/Utils.h" +#include "common/safe_io.h" +#include "common/debug.h" #include "common/errno.h" #include #include @@ -17,6 +19,374 @@ namespace merge_diff { namespace at = argument_types; namespace po = boost::program_options; +static int parse_diff_header(int fd, __u8 *tag, string *from, string *to, uint64_t *size) +{ + int r; + + {//header + char buf[utils::RBD_DIFF_BANNER.size() + 1]; + r = safe_read_exact(fd, buf, utils::RBD_DIFF_BANNER.size()); + if (r < 0) + return r; + + buf[utils::RBD_DIFF_BANNER.size()] = '\0'; + if (strcmp(buf, utils::RBD_DIFF_BANNER.c_str())) { + std::cerr << "invalid banner '" << buf << "', expected '" + << utils::RBD_DIFF_BANNER << "'" << std::endl; + return -EINVAL; + } + } + + while (true) { + r = safe_read_exact(fd, tag, 1); + if (r < 0) + return r; + + if (*tag == 'f') { + r = utils::read_string(fd, 4096, from); // 4k limit to make sure we don't get a garbage string + if (r < 0) + return r; + dout(2) << " from snap " << *from << dendl; + } else if (*tag == 't') { + r = utils::read_string(fd, 4096, to); // 4k limit to make sure we don't get a garbage string + if (r < 0) + return r; + dout(2) << " to snap " << *to << dendl; + } else if (*tag == 's') { + char buf[8]; + r = safe_read_exact(fd, buf, 8); + if (r < 0) + return r; + + bufferlist bl; + bl.append(buf, 8); + bufferlist::iterator p = bl.begin(); + ::decode(*size, p); + } else { + break; + } + } + + return 0; +} + +static int parse_diff_body(int fd, __u8 *tag, uint64_t *offset, uint64_t *length) +{ + int r; + + if (!(*tag)) { + r = safe_read_exact(fd, tag, 1); + if (r < 0) + return r; + } + + if (*tag == 'e') { + offset = 0; + length = 0; + return 0; + } + + if (*tag != 'w' && *tag != 'z') + return -ENOTSUP; + + char buf[16]; + r = safe_read_exact(fd, buf, 16); + if (r < 0) + return r; + + bufferlist bl; + bl.append(buf, 16); + bufferlist::iterator p = bl.begin(); + ::decode(*offset, p); + ::decode(*length, p); + + if (!(*length)) + return -ENOTSUP; + + return 0; +} + +/* + * fd: the diff file to read from + * pd: the diff file to be written into + */ +static int accept_diff_body(int fd, int pd, __u8 tag, uint64_t offset, uint64_t length) +{ + if (tag == 'e') + return 0; + + bufferlist bl; + ::encode(tag, bl); + ::encode(offset, bl); + ::encode(length, bl); + int r; + r = bl.write_fd(pd); + if (r < 0) + return r; + + if (tag == 'w') { + bufferptr bp = buffer::create(length); + r = safe_read_exact(fd, bp.c_str(), length); + if (r < 0) + return r; + bufferlist data; + data.append(bp); + r = data.write_fd(pd); + if (r < 0) + return r; + } + + return 0; +} + +/* + * Merge two diff files into one single file + * Note: It does not do the merging work if + * either of the source diff files is stripped, + * since which complicates the process and is + * rarely used + */ +static int do_merge_diff(const char *first, const char *second, + const char *path, bool no_progress) +{ + utils::ProgressContext pc("Merging image diff", no_progress); + int fd = -1, sd = -1, pd = -1, r; + + string f_from, f_to; + string s_from, s_to; + uint64_t f_size, s_size, pc_size; + + __u8 f_tag = 0, s_tag = 0; + uint64_t f_off = 0, f_len = 0; + uint64_t s_off = 0, s_len = 0; + bool f_end = false, s_end = false; + + bool first_stdin = !strcmp(first, "-"); + if (first_stdin) { + fd = 0; + } else { + fd = open(first, O_RDONLY); + if (fd < 0) { + r = -errno; + std::cerr << "rbd: error opening " << first << std::endl; + goto done; + } + } + + sd = open(second, O_RDONLY); + if (sd < 0) { + r = -errno; + std::cerr << "rbd: error opening " << second << std::endl; + goto done; + } + + if (strcmp(path, "-") == 0) { + pd = 1; + } else { + pd = open(path, O_WRONLY | O_CREAT | O_EXCL, 0644); + if (pd < 0) { + r = -errno; + std::cerr << "rbd: error create " << path << std::endl; + goto done; + } + } + + //We just handle the case like 'banner, [ftag], [ttag], stag, [wztag]*,etag', + // and the (offset,length) in wztag must be ascending order. + + r = parse_diff_header(fd, &f_tag, &f_from, &f_to, &f_size); + if (r < 0) { + std::cerr << "rbd: failed to parse first diff header" << std::endl; + goto done; + } + + r = parse_diff_header(sd, &s_tag, &s_from, &s_to, &s_size); + if (r < 0) { + std::cerr << "rbd: failed to parse second diff header" << std::endl; + goto done; + } + + if (f_to != s_from) { + r = -EINVAL; + std::cerr << "The first TO snapshot must be equal with the second FROM " + << "snapshot, aborting" << std::endl; + goto done; + } + + { + // header + bufferlist bl; + bl.append(utils::RBD_DIFF_BANNER); + + __u8 tag; + if (f_from.size()) { + tag = 'f'; + ::encode(tag, bl); + ::encode(f_from, bl); + } + + if (s_to.size()) { + tag = 't'; + ::encode(tag, bl); + ::encode(s_to, bl); + } + + tag = 's'; + ::encode(tag, bl); + ::encode(s_size, bl); + + r = bl.write_fd(pd); + if (r < 0) { + std::cerr << "rbd: failed to write merged diff header" << std::endl; + goto done; + } + } + if (f_size > s_size) + pc_size = f_size << 1; + else + pc_size = s_size << 1; + + //data block + while (!f_end || !s_end) { + // progress through input + pc.update_progress(f_off + s_off, pc_size); + + if (!f_end && !f_len) { + uint64_t last_off = f_off; + + r = parse_diff_body(fd, &f_tag, &f_off, &f_len); + dout(2) << "first diff data chunk: tag=" << f_tag << ", " + << "off=" << f_off << ", " + << "len=" << f_len << dendl; + if (r < 0) { + std::cerr << "rbd: failed to read first diff data chunk header" + << std::endl; + goto done; + } + + if (f_tag == 'e') { + f_end = true; + f_tag = 'z'; + f_off = f_size; + if (f_size < s_size) + f_len = s_size - f_size; + else + f_len = 0; + } + + if (last_off > f_off) { + r = -ENOTSUP; + std::cerr << "rbd: out-of-order offset from first diff (" + << last_off << " > " << f_off << ")" << std::endl; + goto done; + } + } + + if (!s_end && !s_len) { + uint64_t last_off = s_off; + + r = parse_diff_body(sd, &s_tag, &s_off, &s_len); + dout(2) << "second diff data chunk: tag=" << f_tag << ", " + << "off=" << f_off << ", " + << "len=" << f_len << dendl; + if (r < 0) { + std::cerr << "rbd: failed to read second diff data chunk header" + << std::endl; + goto done; + } + + if (s_tag == 'e') { + s_end = true; + s_off = s_size; + if (s_size < f_size) + s_len = f_size - s_size; + else + s_len = 0; + } + + if (last_off > s_off) { + r = -ENOTSUP; + std::cerr << "rbd: out-of-order offset from second diff (" + << last_off << " > " << s_off << ")" << std::endl; + goto done; + } + } + + if (f_off < s_off && f_len) { + uint64_t delta = s_off - f_off; + if (delta > f_len) + delta = f_len; + r = accept_diff_body(fd, pd, f_tag, f_off, delta); + f_off += delta; + f_len -= delta; + + if (!f_len) { + f_tag = 0; + continue; + } + } + assert(f_off >= s_off); + + if (f_off < s_off + s_len && f_len) { + uint64_t delta = s_off + s_len - f_off; + if (delta > f_len) + delta = f_len; + if (f_tag == 'w') { + if (first_stdin) { + bufferptr bp = buffer::create(delta); + r = safe_read_exact(fd, bp.c_str(), delta); + } else { + r = lseek(fd, delta, SEEK_CUR); + } + if (r < 0) { + std::cerr << "rbd: failed to skip first diff data" << std::endl; + goto done; + } + } + f_off += delta; + f_len -= delta; + + if (!f_len) { + f_tag = 0; + continue; + } + } + assert(f_off >= s_off + s_len); + if (s_len) { + r = accept_diff_body(sd, pd, s_tag, s_off, s_len); + s_off += s_len; + s_len = 0; + s_tag = 0; + } else + assert(f_end && s_end); + continue; + } + + {//tail + __u8 tag = 'e'; + bufferlist bl; + ::encode(tag, bl); + r = bl.write_fd(pd); + } + +done: + if (pd > 2) + close(pd); + if (sd > 2) + close(sd); + if (fd > 2) + close(fd); + + if(r < 0) { + pc.fail(); + if (pd > 2) + unlink(path); + } else + pc.finish(); + + return r; +} + void get_arguments(po::options_description *positional, po::options_description *options) { positional->add_options() @@ -47,6 +417,13 @@ int execute(const po::variables_map &vm) { return r; } + r = do_merge_diff(first_diff.c_str(), second_diff.c_str(), path.c_str(), + vm[at::NO_PROGRESS].as()); + if (r < 0) { + cerr << "rbd: merge-diff error" << std::endl; + return -r; + } + return 0; } diff --git a/src/tools/rbd/action/ObjectMap.cc b/src/tools/rbd/action/ObjectMap.cc index ab23dc6f8bdbe..b14bc72d0ddf8 100644 --- a/src/tools/rbd/action/ObjectMap.cc +++ b/src/tools/rbd/action/ObjectMap.cc @@ -15,6 +15,18 @@ namespace object_map { namespace at = argument_types; namespace po = boost::program_options; +static int do_object_map_rebuild(librbd::Image &image, bool no_progress) +{ + utils::ProgressContext pc("Object Map Rebuild", no_progress); + int r = image.rebuild_object_map(pc); + if (r < 0) { + pc.fail(); + return r; + } + pc.finish(); + return 0; +} + void get_arguments(po::options_description *positional, po::options_description *options) { at::add_image_or_snap_spec_options(positional, options, @@ -43,6 +55,12 @@ int execute(const po::variables_map &vm) { return r; } + r = do_object_map_rebuild(image, vm[at::NO_PROGRESS].as()); + if (r < 0) { + std::cerr << "rbd: rebuilding object map failed: " << cpp_strerror(r) + << std::endl; + return r; + } return 0; } diff --git a/src/tools/rbd/action/Remove.cc b/src/tools/rbd/action/Remove.cc index f7244c92ed466..6c2d2c3e2cd31 100644 --- a/src/tools/rbd/action/Remove.cc +++ b/src/tools/rbd/action/Remove.cc @@ -15,6 +15,19 @@ namespace remove { namespace at = argument_types; namespace po = boost::program_options; +static int do_delete(librbd::RBD &rbd, librados::IoCtx& io_ctx, + const char *imgname, bool no_progress) +{ + utils::ProgressContext pc("Removing image", no_progress); + int r = rbd.remove_with_progress(io_ctx, imgname, pc); + if (r < 0) { + pc.fail(); + return r; + } + pc.finish(); + return 0; +} + void get_arguments(po::options_description *positional, po::options_description *options) { at::add_image_spec_options(positional, options, at::ARGUMENT_MODIFIER_NONE); @@ -40,6 +53,26 @@ int execute(const po::variables_map &vm) { return r; } + librbd::RBD rbd; + r = do_delete(rbd, io_ctx, image_name.c_str(), + vm[at::NO_PROGRESS].as()); + if (r < 0) { + if (r == -ENOTEMPTY) { + std::cerr << "rbd: image has snapshots - these must be deleted" + << " with 'rbd snap purge' before the image can be removed." + << std::endl; + } else if (r == -EBUSY) { + std::cerr << "rbd: error: image still has watchers" + << std::endl + << "This means the image is still open or the client using " + << "it crashed. Try again after closing/unmapping it or " + << "waiting 30s for the crashed client to timeout." + << std::endl; + } else { + std::cerr << "rbd: delete error: " << cpp_strerror(r) << std::endl; + } + return r ; + } return 0; } diff --git a/src/tools/rbd/action/Rename.cc b/src/tools/rbd/action/Rename.cc index a600c5f03d9bb..c0761118e8e36 100644 --- a/src/tools/rbd/action/Rename.cc +++ b/src/tools/rbd/action/Rename.cc @@ -15,6 +15,15 @@ namespace rename { namespace at = argument_types; namespace po = boost::program_options; +static int do_rename(librbd::RBD &rbd, librados::IoCtx& io_ctx, + const char *imgname, const char *destname) +{ + int r = rbd.rename(io_ctx, imgname, destname); + if (r < 0) + return r; + return 0; +} + void get_arguments(po::options_description *positional, po::options_description *options) { at::add_image_spec_options(positional, options, at::ARGUMENT_MODIFIER_SOURCE); @@ -57,6 +66,12 @@ int execute(const po::variables_map &vm) { return r; } + librbd::RBD rbd; + r = do_rename(rbd, io_ctx, image_name.c_str(), dst_image_name.c_str()); + if (r < 0) { + std::cerr << "rbd: rename error: " << cpp_strerror(r) << std::endl; + return r; + } return 0; } diff --git a/src/tools/rbd/action/Resize.cc b/src/tools/rbd/action/Resize.cc index a2ad87896f3f1..aa7a3904b7b27 100644 --- a/src/tools/rbd/action/Resize.cc +++ b/src/tools/rbd/action/Resize.cc @@ -15,6 +15,18 @@ namespace resize { namespace at = argument_types; namespace po = boost::program_options; +static int do_resize(librbd::Image& image, uint64_t size, bool no_progress) +{ + utils::ProgressContext pc("Resizing image", no_progress); + int r = image.resize_with_progress(size, pc); + if (r < 0) { + pc.fail(); + return r; + } + pc.finish(); + return 0; +} + void get_arguments(po::options_description *positional, po::options_description *options) { at::add_image_spec_options(positional, options, at::ARGUMENT_MODIFIER_NONE); @@ -64,6 +76,11 @@ int execute(const po::variables_map &vm) { return -EINVAL; } + r = do_resize(image, size, vm[at::NO_PROGRESS].as()); + if (r < 0) { + std::cerr << "rbd: resize error: " << cpp_strerror(r) << std::endl; + return r; + } return 0; } diff --git a/src/tools/rbd/action/Snap.cc b/src/tools/rbd/action/Snap.cc index a73a693cd4529..a94060f1bfef9 100644 --- a/src/tools/rbd/action/Snap.cc +++ b/src/tools/rbd/action/Snap.cc @@ -4,8 +4,11 @@ #include "tools/rbd/ArgumentTypes.h" #include "tools/rbd/Shell.h" #include "tools/rbd/Utils.h" +#include "include/types.h" +#include "include/stringify.h" #include "common/errno.h" #include "common/Formatter.h" +#include "common/TextTable.h" #include #include @@ -16,6 +19,135 @@ namespace snap { namespace at = argument_types; namespace po = boost::program_options; +int do_list_snaps(librbd::Image& image, Formatter *f) +{ + std::vector snaps; + TextTable t; + int r; + + r = image.snap_list(snaps); + if (r < 0) + return r; + + if (f) { + f->open_array_section("snapshots"); + } else { + t.define_column("SNAPID", TextTable::RIGHT, TextTable::RIGHT); + t.define_column("NAME", TextTable::LEFT, TextTable::LEFT); + t.define_column("SIZE", TextTable::RIGHT, TextTable::RIGHT); + } + + for (std::vector::iterator s = snaps.begin(); + s != snaps.end(); ++s) { + if (f) { + f->open_object_section("snapshot"); + f->dump_unsigned("id", s->id); + f->dump_string("name", s->name); + f->dump_unsigned("size", s->size); + f->close_section(); + } else { + t << s->id << s->name << stringify(prettybyte_t(s->size)) + << TextTable::endrow; + } + } + + if (f) { + f->close_section(); + f->flush(std::cout); + } else if (snaps.size()) { + std::cout << t; + } + + return 0; +} + +int do_add_snap(librbd::Image& image, const char *snapname) +{ + int r = image.snap_create(snapname); + if (r < 0) + return r; + + return 0; +} + +int do_remove_snap(librbd::Image& image, const char *snapname) +{ + int r = image.snap_remove(snapname); + if (r < 0) + return r; + + return 0; +} + +int do_rollback_snap(librbd::Image& image, const char *snapname, + bool no_progress) +{ + utils::ProgressContext pc("Rolling back to snapshot", no_progress); + int r = image.snap_rollback_with_progress(snapname, pc); + if (r < 0) { + pc.fail(); + return r; + } + pc.finish(); + return 0; +} + +int do_purge_snaps(librbd::Image& image, bool no_progress) +{ + utils::ProgressContext pc("Removing all snapshots", no_progress); + std::vector snaps; + bool is_protected = false; + int r = image.snap_list(snaps); + if (r < 0) { + pc.fail(); + return r; + } else if (0 == snaps.size()) { + return 0; + } else { + for (size_t i = 0; i < snaps.size(); ++i) { + r = image.snap_is_protected(snaps[i].name.c_str(), &is_protected); + if (r < 0) { + pc.fail(); + return r; + } else if (is_protected == true) { + pc.fail(); + std::cerr << "\r" << "rbd: snapshot '" << snaps[i].name.c_str() + << "' is protected from removal." << std::endl; + return -EBUSY; + } + } + for (size_t i = 0; i < snaps.size(); ++i) { + r = image.snap_remove(snaps[i].name.c_str()); + if (r < 0) { + pc.fail(); + return r; + } + pc.update_progress(i + 1, snaps.size()); + } + + pc.finish(); + return 0; + } +} + +int do_protect_snap(librbd::Image& image, const char *snapname) +{ + int r = image.snap_protect(snapname); + if (r < 0) + return r; + + return 0; +} + +int do_unprotect_snap(librbd::Image& image, const char *snapname) +{ + int r = image.snap_unprotect(snapname); + if (r < 0) + return r; + + return 0; +} + void get_list_arguments(po::options_description *positional, po::options_description *options) { at::add_image_spec_options(positional, options, at::ARGUMENT_MODIFIER_NONE); @@ -49,6 +181,12 @@ int execute_list(const po::variables_map &vm) { return r; } + r = do_list_snaps(image, formatter.get()); + if (r < 0) { + cerr << "rbd: failed to list snapshots: " << cpp_strerror(r) + << std::endl; + return r; + } return 0; } @@ -78,6 +216,12 @@ int execute_create(const po::variables_map &vm) { return r; } + r = do_add_snap(image, snap_name.c_str()); + if (r < 0) { + cerr << "rbd: failed to create snapshot: " << cpp_strerror(r) + << std::endl; + return r; + } return 0; } @@ -107,6 +251,17 @@ int execute_remove(const po::variables_map &vm) { return r; } + r = do_remove_snap(image, snap_name.c_str()); + if (r < 0) { + if (r == -EBUSY) { + std::cerr << "rbd: snapshot '" << snap_name << "' " + << "is protected from removal." << std::endl; + } else { + std::cerr << "rbd: failed to remove snapshot: " << cpp_strerror(r) + << std::endl; + } + return r; + } return 0; } @@ -137,6 +292,14 @@ int execute_purge(const po::variables_map &vm) { return r; } + r = do_purge_snaps(image, vm[at::NO_PROGRESS].as()); + if (r < 0) { + if (r != -EBUSY) { + std::cerr << "rbd: removing snaps failed: " << cpp_strerror(r) + << std::endl; + } + return r; + } return 0; } @@ -167,6 +330,12 @@ int execute_rollback(const po::variables_map &vm) { return r; } + r = do_rollback_snap(image, snap_name.c_str(), + vm[at::NO_PROGRESS].as()); + if (r < 0) { + std::cerr << "rbd: rollback failed: " << cpp_strerror(r) << std::endl; + return r; + } return 0; } @@ -196,6 +365,12 @@ int execute_protect(const po::variables_map &vm) { return r; } + r = do_protect_snap(image, snap_name.c_str()); + if (r < 0) { + std::cerr << "rbd: protecting snap failed: " << cpp_strerror(r) + << std::endl; + return r; + } return 0; } @@ -225,6 +400,12 @@ int execute_unprotect(const po::variables_map &vm) { return r; } + r = do_unprotect_snap(image, snap_name.c_str()); + if (r < 0) { + std::cerr << "rbd: unprotecting snap failed: " << cpp_strerror(r) + << std::endl; + return r; + } return 0; } diff --git a/src/tools/rbd/action/Status.cc b/src/tools/rbd/action/Status.cc index 5cf4b72f5eff5..da8fe97e2cb57 100644 --- a/src/tools/rbd/action/Status.cc +++ b/src/tools/rbd/action/Status.cc @@ -4,6 +4,7 @@ #include "tools/rbd/ArgumentTypes.h" #include "tools/rbd/Shell.h" #include "tools/rbd/Utils.h" +#include "include/rbd_types.h" #include "common/errno.h" #include "common/Formatter.h" #include @@ -16,6 +17,72 @@ namespace status { namespace at = argument_types; namespace po = boost::program_options; +static int do_show_status(librados::IoCtx &io_ctx, librbd::Image &image, + const char *imgname, Formatter *f) +{ + librbd::image_info_t info; + uint8_t old_format; + int r; + std::string header_oid; + std::list watchers; + + r = image.old_format(&old_format); + if (r < 0) + return r; + + if (old_format) { + header_oid = imgname; + header_oid += RBD_SUFFIX; + } else { + r = image.stat(info, sizeof(info)); + if (r < 0) + return r; + + char prefix[RBD_MAX_BLOCK_NAME_SIZE + 1]; + strncpy(prefix, info.block_name_prefix, RBD_MAX_BLOCK_NAME_SIZE); + prefix[RBD_MAX_BLOCK_NAME_SIZE] = '\0'; + + header_oid = RBD_HEADER_PREFIX; + header_oid.append(prefix + strlen(RBD_DATA_PREFIX)); + } + + r = io_ctx.list_watchers(header_oid, &watchers); + if (r < 0) + return r; + + if (f) + f->open_object_section("status"); + + if (f) { + f->open_object_section("watchers"); + for (std::list::iterator i = watchers.begin(); i != watchers.end(); ++i) { + f->open_object_section("watcher"); + f->dump_string("address", i->addr); + f->dump_unsigned("client", i->watcher_id); + f->dump_unsigned("cookie", i->cookie); + f->close_section(); + } + f->close_section(); + } else { + if (watchers.size()) { + std::cout << "Watchers:" << std::endl; + for (std::list::iterator i = watchers.begin(); + i != watchers.end(); ++i) { + std::cout << "\twatcher=" << i->addr << " client." << i->watcher_id + << " cookie=" << i->cookie << std::endl; + } + } else { + std::cout << "Watchers: none" << std::endl; + } + } + if (f) { + f->close_section(); + f->flush(std::cout); + } + + return 0; +} + void get_arguments(po::options_description *positional, po::options_description *options) { at::add_image_spec_options(positional, options, at::ARGUMENT_MODIFIER_NONE); @@ -49,6 +116,11 @@ int execute(const po::variables_map &vm) { return r; } + r = do_show_status(io_ctx, image, image_name.c_str(), formatter.get()); + if (r < 0) { + std::cerr << "rbd: show status failed: " << cpp_strerror(r) << std::endl; + return r; + } return 0; } diff --git a/src/tools/rbd/action/Watch.cc b/src/tools/rbd/action/Watch.cc index 1570ca8d14692..3e532550d9309 100644 --- a/src/tools/rbd/action/Watch.cc +++ b/src/tools/rbd/action/Watch.cc @@ -4,6 +4,7 @@ #include "tools/rbd/ArgumentTypes.h" #include "tools/rbd/Shell.h" #include "tools/rbd/Utils.h" +#include "include/rbd_types.h" #include "common/errno.h" #include #include @@ -46,6 +47,54 @@ private: std::string m_header_oid; }; +static int do_watch(librados::IoCtx& pp, librbd::Image &image, + const char *imgname) +{ + uint8_t old_format; + int r = image.old_format(&old_format); + if (r < 0) { + std::cerr << "failed to query format" << std::endl; + return r; + } + + std::string header_oid; + if (old_format != 0) { + header_oid = std::string(imgname) + RBD_SUFFIX; + } else { + librbd::image_info_t info; + r = image.stat(info, sizeof(info)); + if (r < 0) { + std::cerr << "failed to stat image" << std::endl; + return r; + } + + char prefix[RBD_MAX_BLOCK_NAME_SIZE + 1]; + strncpy(prefix, info.block_name_prefix, RBD_MAX_BLOCK_NAME_SIZE); + prefix[RBD_MAX_BLOCK_NAME_SIZE] = '\0'; + + std::string image_id(prefix + strlen(RBD_DATA_PREFIX)); + header_oid = RBD_HEADER_PREFIX + image_id; + } + + uint64_t cookie; + RbdWatchCtx ctx(pp, imgname, header_oid); + r = pp.watch2(header_oid, &cookie, &ctx); + if (r < 0) { + std::cerr << "rbd: watch failed" << std::endl; + return r; + } + + std::cout << "press enter to exit..." << std::endl; + getchar(); + + r = pp.unwatch2(cookie); + if (r < 0) { + std::cerr << "rbd: unwatch failed" << std::endl; + return r; + } + return 0; +} + void get_arguments(po::options_description *positional, po::options_description *options) { at::add_image_spec_options(positional, options, at::ARGUMENT_MODIFIER_NONE); @@ -72,6 +121,11 @@ int execute(const po::variables_map &vm) { return r; } + r = do_watch(io_ctx, image, image_name.c_str()); + if (r < 0) { + std::cerr << "rbd: watch failed: " << cpp_strerror(r) << std::endl; + return r; + } return 0; } -- 2.39.5