// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- // vim: ts=8 sw=2 smarttab #include "include/compat.h" #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/encoding.h" #include #include #include #include #include namespace rbd { namespace action { namespace export_full { struct ExportDiffContext { librbd::Image *image; int fd; int export_format; uint64_t totalsize; utils::ProgressContext pc; OrderedThrottle throttle; ExportDiffContext(librbd::Image *i, int f, uint64_t t, int max_ops, bool no_progress, int eformat) : image(i), fd(f), export_format(eformat), 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, int export_format) : m_export_diff_context(edc), m_offset(offset), m_length(length), m_exists(exists), m_export_format(export_format) { } 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, edc->export_format); return context->send(); } protected: void finish(int r) override { 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, m_export_format); 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; int m_export_format; bufferlist m_read_data; static int write_extent(ExportDiffContext *edc, uint64_t offset, uint64_t length, bool exists, int export_format) { // extent bufferlist bl; __u8 tag = exists ? RBD_DIFF_WRITE : RBD_DIFF_ZERO; uint64_t len = 0; ::encode(tag, bl); if (export_format == 2) { if (tag == RBD_DIFF_WRITE) len = 8 + 8 + length; else len = 8 + 8; ::encode(len, bl); } ::encode(offset, bl); ::encode(length, bl); int r = bl.write_fd(edc->fd); edc->pc.update_progress(offset, edc->totalsize); return r; } }; int do_export_diff_fd(librbd::Image& image, const char *fromsnapname, const char *endsnapname, bool whole_object, int fd, bool no_progress, int export_format) { int r; librbd::image_info_t info; r = image.stat(info, sizeof(info)); if (r < 0) return r; { // header bufferlist bl; if (export_format == 1) bl.append(utils::RBD_DIFF_BANNER); else bl.append(utils::RBD_DIFF_BANNER_V2); __u8 tag; uint64_t len = 0; if (fromsnapname) { tag = RBD_DIFF_FROM_SNAP; ::encode(tag, bl); std::string from(fromsnapname); if (export_format == 2) { len = from.length() + 4; ::encode(len, bl); } ::encode(from, bl); } if (endsnapname) { tag = RBD_DIFF_TO_SNAP; ::encode(tag, bl); std::string to(endsnapname); if (export_format == 2) { len = to.length() + 4; ::encode(len, bl); } ::encode(to, bl); } tag = RBD_DIFF_IMAGE_SIZE; ::encode(tag, bl); uint64_t endsize = info.size; if (export_format == 2) { len = 8; ::encode(len, bl); } ::encode(endsize, bl); r = bl.write_fd(fd); if (r < 0) { return r; } } ExportDiffContext edc(&image, fd, info.size, g_conf->get_val("rbd_concurrent_management_ops"), no_progress, export_format); 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 = RBD_DIFF_END; bufferlist bl; ::encode(tag, bl); r = bl.write_fd(fd); } out: if (r < 0) edc.pc.fail(); else edc.pc.finish(); return r; } int do_export_diff(librbd::Image& image, const char *fromsnapname, const char *endsnapname, bool whole_object, const char *path, bool no_progress) { int r; int fd; if (strcmp(path, "-") == 0) fd = STDOUT_FILENO; else fd = open(path, O_WRONLY | O_CREAT | O_EXCL, 0644); if (fd < 0) return -errno; r = do_export_diff_fd(image, fromsnapname, endsnapname, whole_object, fd, no_progress, 1); if (fd != 1) close(fd); if (r < 0 && fd != 1) { remove(path); } return r; } namespace at = argument_types; namespace po = boost::program_options; void get_arguments_diff(po::options_description *positional, po::options_description *options) { at::add_image_or_snap_spec_options(positional, options, at::ARGUMENT_MODIFIER_SOURCE); at::add_path_options(positional, options, "export file (or '-' for stdout)"); options->add_options() (at::FROM_SNAPSHOT_NAME.c_str(), po::value(), "snapshot starting point") (at::WHOLE_OBJECT.c_str(), po::bool_switch(), "compare whole object"); at::add_no_progress_option(options); } int execute_diff(const po::variables_map &vm) { size_t arg_index = 0; std::string pool_name; std::string image_name; std::string snap_name; int r = utils::get_pool_image_snapshot_names( vm, at::ARGUMENT_MODIFIER_SOURCE, &arg_index, &pool_name, &image_name, &snap_name, utils::SNAPSHOT_PRESENCE_PERMITTED, utils::SPEC_VALIDATION_NONE); if (r < 0) { return r; } std::string path; r = utils::get_path(vm, utils::get_positional_argument(vm, 1), &path); if (r < 0) { return r; } std::string from_snap_name; if (vm.count(at::FROM_SNAPSHOT_NAME)) { from_snap_name = vm[at::FROM_SNAPSHOT_NAME].as(); } librados::Rados rados; librados::IoCtx io_ctx; librbd::Image image; r = utils::init_and_open_image(pool_name, image_name, "", snap_name, true, &rados, &io_ctx, &image); if (r < 0) { 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; } Shell::SwitchArguments switched_arguments({at::WHOLE_OBJECT}); Shell::Action action_diff( {"export-diff"}, {}, "Export incremental diff to file.", "", &get_arguments_diff, &execute_diff); class C_Export : public Context { public: C_Export(SimpleThrottle &simple_throttle, librbd::Image &image, uint64_t fd_offset, 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_dest_offset(fd_offset), 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); } } void finish(int r) override { 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_dest_offset, SEEK_SET); if (chkret != m_dest_offset) { cerr << "rbd: error seeking destination image to offset " << m_dest_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_dest_offset << std::endl; } } private: librbd::RBD::AioCompletion *m_aio_completion; SimpleThrottle &m_throttle; librbd::Image &m_image; bufferlist m_bufferlist; uint64_t m_dest_offset; uint64_t m_offset; uint64_t m_length; int m_fd; }; static int do_export_v2(librbd::Image& image, librbd::image_info_t &info, int fd, uint64_t period, int max_concurrent_ops, utils::ProgressContext &pc) { int r = 0; // header bufferlist bl; bl.append(utils::RBD_IMAGE_BANNER_V2); __u8 tag; uint64_t length; // encode order tag = RBD_EXPORT_IMAGE_ORDER; length = 8; ::encode(tag, bl); ::encode(length, bl); ::encode(uint64_t(info.order), bl); // encode features tag = RBD_EXPORT_IMAGE_FEATURES; uint64_t features; image.features(&features); length = 8; ::encode(tag, bl); ::encode(length, bl); ::encode(features, bl); // encode stripe_unit and stripe_count tag = RBD_EXPORT_IMAGE_STRIPE_UNIT; uint64_t stripe_unit; stripe_unit = image.get_stripe_unit(); length = 8; ::encode(tag, bl); ::encode(length, bl); ::encode(stripe_unit, bl); tag = RBD_EXPORT_IMAGE_STRIPE_COUNT; uint64_t stripe_count; stripe_count = image.get_stripe_count(); length = 8; ::encode(tag, bl); ::encode(length, bl); ::encode(stripe_count, bl); // encode end tag tag = RBD_EXPORT_IMAGE_END; ::encode(tag, bl); // write bl to fd. r = bl.write_fd(fd); if (r < 0) { return r; } // header for snapshots bl.clear(); bl.append(utils::RBD_IMAGE_DIFFS_BANNER_V2); std::vector snaps; r = image.snap_list(snaps); if (r < 0) { return r; } uint64_t diff_num = snaps.size() + 1; ::encode(diff_num, bl); r = bl.write_fd(fd); if (r < 0) { return r; } const char *last_snap = NULL; for (size_t i = 0; i < snaps.size(); ++i) { utils::snap_set(image, snaps[i].name.c_str()); r = do_export_diff_fd(image, last_snap, snaps[i].name.c_str(), false, fd, true, 2); if (r < 0) { return r; } pc.update_progress(i, snaps.size() + 1); last_snap = snaps[i].name.c_str(); } utils::snap_set(image, std::string("")); r = do_export_diff_fd(image, last_snap, nullptr, false, fd, true, 2); if (r < 0) { return r; } pc.update_progress(snaps.size() + 1, snaps.size() + 1); return r; } static int do_export_v1(librbd::Image& image, librbd::image_info_t &info, int fd, uint64_t period, int max_concurrent_ops, utils::ProgressContext &pc) { int r = 0; size_t file_size = 0; SimpleThrottle throttle(max_concurrent_ops, false); 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, file_size + offset, offset, length, fd); ctx->send(); pc.update_progress(offset, info.size); } file_size += info.size; r = throttle.wait_for_ret(); if (fd != 1) { if (r >= 0) { r = ftruncate(fd, file_size); if (r < 0) return r; uint64_t chkret = lseek64(fd, file_size, SEEK_SET); if (chkret != file_size) r = errno; } } return r; } static int do_export(librbd::Image& image, const char *path, bool no_progress, int export_format) { 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 = g_conf->get_val("rbd_concurrent_management_ops"); fd = open(path, O_WRONLY | O_CREAT | O_EXCL, 0644); if (fd < 0) { return -errno; } #ifdef HAVE_POSIX_FADVISE posix_fadvise(fd, 0, 0, POSIX_FADV_SEQUENTIAL); #endif } utils::ProgressContext pc("Exporting image", no_progress); uint64_t period = image.get_stripe_count() * (1ull << info.order); if (export_format == 1) r = do_export_v1(image, info, fd, period, max_concurrent_ops, pc); else r = do_export_v2(image, info, fd, period, max_concurrent_ops, pc); if (r < 0) pc.fail(); else pc.finish(); if (!to_stdout) close(fd); return r; } void get_arguments(po::options_description *positional, po::options_description *options) { at::add_image_or_snap_spec_options(positional, options, at::ARGUMENT_MODIFIER_SOURCE); at::add_path_options(positional, options, "export file (or '-' for stdout)"); at::add_no_progress_option(options); at::add_export_format_option(options); } int execute(const po::variables_map &vm) { size_t arg_index = 0; std::string pool_name; std::string image_name; std::string snap_name; int r = utils::get_pool_image_snapshot_names( vm, at::ARGUMENT_MODIFIER_SOURCE, &arg_index, &pool_name, &image_name, &snap_name, utils::SNAPSHOT_PRESENCE_PERMITTED, utils::SPEC_VALIDATION_NONE); if (r < 0) { return r; } std::string path; r = utils::get_path(vm, utils::get_positional_argument(vm, 1), &path); if (r < 0) { return r; } librados::Rados rados; librados::IoCtx io_ctx; librbd::Image image; r = utils::init_and_open_image(pool_name, image_name, "", snap_name, true, &rados, &io_ctx, &image); if (r < 0) { return r; } int format = 1; if (vm.count("export-format")) format = vm["export-format"].as(); r = do_export(image, path.c_str(), vm[at::NO_PROGRESS].as(), format); if (r < 0) { std::cerr << "rbd: export error: " << cpp_strerror(r) << std::endl; return r; } return 0; } Shell::Action action( {"export"}, {}, "Export image to file.", "", &get_arguments, &execute); } // namespace export_full } // namespace action } // namespace rbd