1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
4 #include "include/compat.h"
5 #include "tools/rbd/ArgumentTypes.h"
6 #include "tools/rbd/Shell.h"
7 #include "tools/rbd/Utils.h"
8 #include "include/Context.h"
9 #include "common/errno.h"
10 #include "common/Throttle.h"
11 #include "include/encoding.h"
15 #include <boost/program_options.hpp>
16 #include <boost/scope_exit.hpp>
20 namespace export_full
{
22 struct ExportDiffContext
{
27 utils::ProgressContext pc
;
28 OrderedThrottle throttle
;
30 ExportDiffContext(librbd::Image
*i
, int f
, uint64_t t
, int max_ops
,
31 bool no_progress
, int eformat
) :
32 image(i
), fd(f
), export_format(eformat
), totalsize(t
), pc("Exporting image", no_progress
),
33 throttle(max_ops
, true) {
37 class C_ExportDiff
: public Context
{
39 C_ExportDiff(ExportDiffContext
*edc
, uint64_t offset
, uint64_t length
,
40 bool exists
, int export_format
)
41 : m_export_diff_context(edc
), m_offset(offset
), m_length(length
),
42 m_exists(exists
), m_export_format(export_format
) {
46 if (m_export_diff_context
->throttle
.pending_error()) {
47 return m_export_diff_context
->throttle
.wait_for_ret();
50 C_OrderedThrottle
*ctx
= m_export_diff_context
->throttle
.start_op(this);
52 librbd::RBD::AioCompletion
*aio_completion
=
53 new librbd::RBD::AioCompletion(ctx
, &utils::aio_context_callback
);
55 int op_flags
= LIBRADOS_OP_FLAG_FADVISE_NOCACHE
;
56 int r
= m_export_diff_context
->image
->aio_read2(
57 m_offset
, m_length
, m_read_data
, aio_completion
, op_flags
);
59 aio_completion
->release();
68 static int export_diff_cb(uint64_t offset
, size_t length
, int exists
,
70 ExportDiffContext
*edc
= reinterpret_cast<ExportDiffContext
*>(arg
);
72 C_ExportDiff
*context
= new C_ExportDiff(edc
, offset
, length
, exists
, edc
->export_format
);
73 return context
->send();
77 void finish(int r
) override
{
80 m_exists
= !m_read_data
.is_zero();
82 r
= write_extent(m_export_diff_context
, m_offset
, m_length
, m_exists
, m_export_format
);
83 if (r
== 0 && m_exists
) {
84 r
= m_read_data
.write_fd(m_export_diff_context
->fd
);
87 m_export_diff_context
->throttle
.end_op(r
);
91 ExportDiffContext
*m_export_diff_context
;
96 bufferlist m_read_data
;
98 static int write_extent(ExportDiffContext
*edc
, uint64_t offset
,
99 uint64_t length
, bool exists
, int export_format
) {
102 __u8 tag
= exists
? RBD_DIFF_WRITE
: RBD_DIFF_ZERO
;
105 if (export_format
== 2) {
106 if (tag
== RBD_DIFF_WRITE
)
107 len
= 8 + 8 + length
;
112 ::encode(offset
, bl
);
113 ::encode(length
, bl
);
114 int r
= bl
.write_fd(edc
->fd
);
116 edc
->pc
.update_progress(offset
, edc
->totalsize
);
122 int do_export_diff_fd(librbd::Image
& image
, const char *fromsnapname
,
123 const char *endsnapname
, bool whole_object
,
124 int fd
, bool no_progress
, int export_format
)
127 librbd::image_info_t info
;
129 r
= image
.stat(info
, sizeof(info
));
136 if (export_format
== 1)
137 bl
.append(utils::RBD_DIFF_BANNER
);
139 bl
.append(utils::RBD_DIFF_BANNER_V2
);
144 tag
= RBD_DIFF_FROM_SNAP
;
146 std::string
from(fromsnapname
);
147 if (export_format
== 2) {
148 len
= from
.length() + 4;
155 tag
= RBD_DIFF_TO_SNAP
;
157 std::string
to(endsnapname
);
158 if (export_format
== 2) {
159 len
= to
.length() + 4;
165 if (endsnapname
&& export_format
== 2) {
166 tag
= RBD_SNAP_PROTECTION_STATUS
;
168 bool is_protected
= false;
169 r
= image
.snap_is_protected(endsnapname
, &is_protected
);
175 encode(is_protected
, bl
);
178 tag
= RBD_DIFF_IMAGE_SIZE
;
180 uint64_t endsize
= info
.size
;
181 if (export_format
== 2) {
185 ::encode(endsize
, bl
);
192 ExportDiffContext
edc(&image
, fd
, info
.size
,
193 g_conf
->get_val
<int64_t>("rbd_concurrent_management_ops"),
194 no_progress
, export_format
);
195 r
= image
.diff_iterate2(fromsnapname
, 0, info
.size
, true, whole_object
,
196 &C_ExportDiff::export_diff_cb
, (void *)&edc
);
201 r
= edc
.throttle
.wait_for_ret();
207 __u8 tag
= RBD_DIFF_END
;
222 int do_export_diff(librbd::Image
& image
, const char *fromsnapname
,
223 const char *endsnapname
, bool whole_object
,
224 const char *path
, bool no_progress
)
229 if (strcmp(path
, "-") == 0)
232 fd
= open(path
, O_WRONLY
| O_CREAT
| O_EXCL
, 0644);
236 r
= do_export_diff_fd(image
, fromsnapname
, endsnapname
, whole_object
, fd
, no_progress
, 1);
240 if (r
< 0 && fd
!= 1) {
248 namespace at
= argument_types
;
249 namespace po
= boost::program_options
;
251 void get_arguments_diff(po::options_description
*positional
,
252 po::options_description
*options
) {
253 at::add_image_or_snap_spec_options(positional
, options
,
254 at::ARGUMENT_MODIFIER_SOURCE
);
255 at::add_path_options(positional
, options
,
256 "export file (or '-' for stdout)");
257 options
->add_options()
258 (at::FROM_SNAPSHOT_NAME
.c_str(), po::value
<std::string
>(),
259 "snapshot starting point")
260 (at::WHOLE_OBJECT
.c_str(), po::bool_switch(), "compare whole object");
261 at::add_no_progress_option(options
);
264 int execute_diff(const po::variables_map
&vm
) {
265 size_t arg_index
= 0;
266 std::string pool_name
;
267 std::string image_name
;
268 std::string snap_name
;
269 int r
= utils::get_pool_image_snapshot_names(
270 vm
, at::ARGUMENT_MODIFIER_SOURCE
, &arg_index
, &pool_name
, &image_name
,
271 &snap_name
, utils::SNAPSHOT_PRESENCE_PERMITTED
,
272 utils::SPEC_VALIDATION_NONE
);
278 r
= utils::get_path(vm
, utils::get_positional_argument(vm
, 1), &path
);
283 std::string from_snap_name
;
284 if (vm
.count(at::FROM_SNAPSHOT_NAME
)) {
285 from_snap_name
= vm
[at::FROM_SNAPSHOT_NAME
].as
<std::string
>();
288 librados::Rados rados
;
289 librados::IoCtx io_ctx
;
291 r
= utils::init_and_open_image(pool_name
, image_name
, "", snap_name
, true,
292 &rados
, &io_ctx
, &image
);
297 r
= do_export_diff(image
,
298 from_snap_name
.empty() ? nullptr : from_snap_name
.c_str(),
299 snap_name
.empty() ? nullptr : snap_name
.c_str(),
300 vm
[at::WHOLE_OBJECT
].as
<bool>(), path
.c_str(),
301 vm
[at::NO_PROGRESS
].as
<bool>());
303 std::cerr
<< "rbd: export-diff error: " << cpp_strerror(r
) << std::endl
;
309 Shell::SwitchArguments
switched_arguments({at::WHOLE_OBJECT
});
310 Shell::Action
action_diff(
311 {"export-diff"}, {}, "Export incremental diff to file.", "",
312 &get_arguments_diff
, &execute_diff
);
314 class C_Export
: public Context
317 C_Export(SimpleThrottle
&simple_throttle
, librbd::Image
&image
,
318 uint64_t fd_offset
, uint64_t offset
, uint64_t length
, int fd
)
320 new librbd::RBD::AioCompletion(this, &utils::aio_context_callback
)),
321 m_throttle(simple_throttle
), m_image(image
), m_dest_offset(fd_offset
),
322 m_offset(offset
), m_length(length
), m_fd(fd
)
328 m_throttle
.start_op();
330 int op_flags
= LIBRADOS_OP_FLAG_FADVISE_SEQUENTIAL
|
331 LIBRADOS_OP_FLAG_FADVISE_NOCACHE
;
332 int r
= m_image
.aio_read2(m_offset
, m_length
, m_bufferlist
,
333 m_aio_completion
, op_flags
);
335 cerr
<< "rbd: error requesting read from source image" << std::endl
;
336 m_aio_completion
->release();
337 m_throttle
.end_op(r
);
341 void finish(int r
) override
343 BOOST_SCOPE_EXIT((&m_throttle
) (&r
))
345 m_throttle
.end_op(r
);
346 } BOOST_SCOPE_EXIT_END
349 cerr
<< "rbd: error reading from source image at offset "
350 << m_offset
<< ": " << cpp_strerror(r
) << std::endl
;
354 assert(m_bufferlist
.length() == static_cast<size_t>(r
));
355 if (m_fd
!= STDOUT_FILENO
) {
356 if (m_bufferlist
.is_zero()) {
360 uint64_t chkret
= lseek64(m_fd
, m_dest_offset
, SEEK_SET
);
361 if (chkret
!= m_dest_offset
) {
362 cerr
<< "rbd: error seeking destination image to offset "
363 << m_dest_offset
<< std::endl
;
369 r
= m_bufferlist
.write_fd(m_fd
);
371 cerr
<< "rbd: error writing to destination image at offset "
372 << m_dest_offset
<< std::endl
;
377 librbd::RBD::AioCompletion
*m_aio_completion
;
378 SimpleThrottle
&m_throttle
;
379 librbd::Image
&m_image
;
380 bufferlist m_bufferlist
;
381 uint64_t m_dest_offset
;
387 const uint32_t MAX_KEYS
= 64;
389 static int do_export_v2(librbd::Image
& image
, librbd::image_info_t
&info
, int fd
,
390 uint64_t period
, int max_concurrent_ops
, utils::ProgressContext
&pc
)
395 bl
.append(utils::RBD_IMAGE_BANNER_V2
);
400 tag
= RBD_EXPORT_IMAGE_ORDER
;
403 ::encode(length
, bl
);
404 ::encode(uint64_t(info
.order
), bl
);
407 tag
= RBD_EXPORT_IMAGE_FEATURES
;
409 image
.features(&features
);
412 ::encode(length
, bl
);
413 ::encode(features
, bl
);
415 // encode stripe_unit and stripe_count
416 tag
= RBD_EXPORT_IMAGE_STRIPE_UNIT
;
417 uint64_t stripe_unit
;
418 stripe_unit
= image
.get_stripe_unit();
421 ::encode(length
, bl
);
422 ::encode(stripe_unit
, bl
);
424 tag
= RBD_EXPORT_IMAGE_STRIPE_COUNT
;
425 uint64_t stripe_count
;
426 stripe_count
= image
.get_stripe_count();
429 ::encode(length
, bl
);
430 ::encode(stripe_count
, bl
);
432 //retrieve metadata of image
433 std::map
<std::string
, string
> imagemetas
;
434 std::string last_key
;
435 bool more_results
= true;
436 while (more_results
) {
437 std::map
<std::string
, bufferlist
> pairs
;
438 r
= image
.metadata_list(last_key
, MAX_KEYS
, &pairs
);
440 std::cerr
<< "failed to retrieve metadata of image : " << cpp_strerror(r
)
445 if (!pairs
.empty()) {
446 last_key
= pairs
.rbegin()->first
;
448 for (auto kv
: pairs
) {
449 std::string key
= kv
.first
;
450 std::string
val(kv
.second
.c_str(), kv
.second
.length());
451 imagemetas
[key
] = val
;
454 more_results
= (pairs
.size() == MAX_KEYS
);
457 //encode imageMeta key and value
458 for (std::map
<std::string
, string
>::iterator it
= imagemetas
.begin();
459 it
!= imagemetas
.end(); ++it
) {
460 string key
= it
->first
;
461 string value
= it
->second
;
463 tag
= RBD_EXPORT_IMAGE_META
;
464 length
= key
.length() + value
.length() + 4 * 2;
466 ::encode(length
, bl
);
472 tag
= RBD_EXPORT_IMAGE_END
;
481 // header for snapshots
483 bl
.append(utils::RBD_IMAGE_DIFFS_BANNER_V2
);
485 std::vector
<librbd::snap_info_t
> snaps
;
486 r
= image
.snap_list(snaps
);
491 uint64_t diff_num
= snaps
.size() + 1;
492 ::encode(diff_num
, bl
);
499 const char *last_snap
= NULL
;
500 for (size_t i
= 0; i
< snaps
.size(); ++i
) {
501 utils::snap_set(image
, snaps
[i
].name
.c_str());
502 r
= do_export_diff_fd(image
, last_snap
, snaps
[i
].name
.c_str(), false, fd
, true, 2);
506 pc
.update_progress(i
, snaps
.size() + 1);
507 last_snap
= snaps
[i
].name
.c_str();
509 utils::snap_set(image
, std::string(""));
510 r
= do_export_diff_fd(image
, last_snap
, nullptr, false, fd
, true, 2);
514 pc
.update_progress(snaps
.size() + 1, snaps
.size() + 1);
518 static int do_export_v1(librbd::Image
& image
, librbd::image_info_t
&info
, int fd
,
519 uint64_t period
, int max_concurrent_ops
, utils::ProgressContext
&pc
)
522 size_t file_size
= 0;
523 SimpleThrottle
throttle(max_concurrent_ops
, false);
524 for (uint64_t offset
= 0; offset
< info
.size
; offset
+= period
) {
525 if (throttle
.pending_error()) {
529 uint64_t length
= min(period
, info
.size
- offset
);
530 C_Export
*ctx
= new C_Export(throttle
, image
, file_size
+ offset
, offset
, length
, fd
);
533 pc
.update_progress(offset
, info
.size
);
536 file_size
+= info
.size
;
537 r
= throttle
.wait_for_ret();
540 r
= ftruncate(fd
, file_size
);
544 uint64_t chkret
= lseek64(fd
, file_size
, SEEK_SET
);
545 if (chkret
!= file_size
)
552 static int do_export(librbd::Image
& image
, const char *path
, bool no_progress
, int export_format
)
554 librbd::image_info_t info
;
555 int64_t r
= image
.stat(info
, sizeof(info
));
560 int max_concurrent_ops
;
561 bool to_stdout
= (strcmp(path
, "-") == 0);
564 max_concurrent_ops
= 1;
566 max_concurrent_ops
= g_conf
->get_val
<int64_t>("rbd_concurrent_management_ops");
567 fd
= open(path
, O_WRONLY
| O_CREAT
| O_EXCL
, 0644);
571 #ifdef HAVE_POSIX_FADVISE
572 posix_fadvise(fd
, 0, 0, POSIX_FADV_SEQUENTIAL
);
576 utils::ProgressContext
pc("Exporting image", no_progress
);
577 uint64_t period
= image
.get_stripe_count() * (1ull << info
.order
);
579 if (export_format
== 1)
580 r
= do_export_v1(image
, info
, fd
, period
, max_concurrent_ops
, pc
);
582 r
= do_export_v2(image
, info
, fd
, period
, max_concurrent_ops
, pc
);
593 void get_arguments(po::options_description
*positional
,
594 po::options_description
*options
) {
595 at::add_image_or_snap_spec_options(positional
, options
,
596 at::ARGUMENT_MODIFIER_SOURCE
);
597 at::add_path_options(positional
, options
,
598 "export file (or '-' for stdout)");
599 at::add_no_progress_option(options
);
600 at::add_export_format_option(options
);
603 int execute(const po::variables_map
&vm
) {
604 size_t arg_index
= 0;
605 std::string pool_name
;
606 std::string image_name
;
607 std::string snap_name
;
608 int r
= utils::get_pool_image_snapshot_names(
609 vm
, at::ARGUMENT_MODIFIER_SOURCE
, &arg_index
, &pool_name
, &image_name
,
610 &snap_name
, utils::SNAPSHOT_PRESENCE_PERMITTED
,
611 utils::SPEC_VALIDATION_NONE
);
617 r
= utils::get_path(vm
, utils::get_positional_argument(vm
, 1), &path
);
622 librados::Rados rados
;
623 librados::IoCtx io_ctx
;
625 r
= utils::init_and_open_image(pool_name
, image_name
, "", snap_name
, true,
626 &rados
, &io_ctx
, &image
);
632 if (vm
.count("export-format"))
633 format
= vm
["export-format"].as
<uint64_t>();
635 r
= do_export(image
, path
.c_str(), vm
[at::NO_PROGRESS
].as
<bool>(), format
);
637 std::cerr
<< "rbd: export error: " << cpp_strerror(r
) << std::endl
;
643 Shell::Action
action(
644 {"export"}, {}, "Export image to file.", "", &get_arguments
, &execute
);
646 } // namespace export_full
647 } // namespace action