From: Ilya Dryomov Date: Sat, 15 Oct 2022 16:31:45 +0000 (+0200) Subject: librbd: reduce overlap and respect area when pruning parent extents X-Git-Tag: v18.1.0~754^2~10 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=d47bb2a2db0d5e9373137bd454cb31c3cdfc06a4;p=ceph.git librbd: reduce overlap and respect area when pruning parent extents DATA area in the parent may be smaller than the part of DATA area in the clone that is still within the overlap. This would occur e.g. in LUKS2-formatted parent + LUKS1-formatted clone case, due to LUKS2 header usually being bigger than LUKS1 header: parent: raw size = 64M LUKS2 header area = 16M data area = 48M clone: raw size = 64M (raw overlap 64M) LUKS1 header area = 4M data area = 60M Currently, because parent extents are pruned only according to raw overlap (64M), the clone ends up attempting to reach the parent for all of its data area (60M < 64M) even though the parent only has 48M worth of data. All kinds of bugs ensue for 48M..60M offsets and this range basically becomes inaccessible to the user. A related issue is that prune_parent_extents() ignores area. Signed-off-by: Ilya Dryomov --- diff --git a/src/librbd/ImageCtx.cc b/src/librbd/ImageCtx.cc index 8f6413d0e40..01b7847c086 100644 --- a/src/librbd/ImageCtx.cc +++ b/src/librbd/ImageCtx.cc @@ -545,15 +545,15 @@ librados::IoCtx duplicate_io_ctx(librados::IoCtx& io_ctx) { return 0; } - auto [data_size, data_area] = io::util::raw_to_area_offset(*this, raw_size); - ceph_assert(data_size <= raw_size && data_area == io::ImageArea::DATA); + auto size = io::util::raw_to_area_offset(*this, raw_size); + ceph_assert(size.first <= raw_size && size.second == io::ImageArea::DATA); switch (area) { case io::ImageArea::DATA: - return data_size; + return size.first; case io::ImageArea::CRYPTO_HEADER: // CRYPTO_HEADER area ends where DATA area begins - return raw_size - data_size; + return raw_size - size.first; default: ceph_abort(); } @@ -701,48 +701,74 @@ librados::IoCtx duplicate_io_ctx(librados::IoCtx& io_ctx) { if (migration_write) { // don't reduce migration write overlap -- it may be larger as // it's the largest overlap across snapshots by construction - return io::util::remap_offset(*this, raw_overlap); + return io::util::raw_to_area_offset(*this, raw_overlap); } if (raw_overlap == 0 || parent == nullptr) { // image opened with OPEN_FLAG_SKIP_OPEN_PARENT -> no overlap - return io::util::remap_offset(*this, 0); + return io::util::raw_to_area_offset(*this, 0); } // DATA area in the parent may be smaller than the part of DATA // area in the clone that is still within the overlap (e.g. for // LUKS2-encrypted parent + LUKS1-encrypted clone, due to LUKS2 // header usually being bigger than LUKS1 header) - auto overlap = io::util::remap_offset(*this, raw_overlap); + auto overlap = io::util::raw_to_area_offset(*this, raw_overlap); std::shared_lock parent_image_locker(parent->image_lock); overlap.first = std::min(overlap.first, parent->get_area_size(overlap.second)); return overlap; } - void ImageCtx::register_watch(Context *on_finish) { - ceph_assert(image_watcher != NULL); - image_watcher->register_watch(on_finish); - } + uint64_t ImageCtx::prune_parent_extents(io::Extents& image_extents, + io::ImageArea area, + uint64_t raw_overlap, + bool migration_write) const { + ceph_assert(ceph_mutex_is_locked(image_lock)); + ldout(cct, 10) << __func__ << ": image_extents=" << image_extents + << " area=" << area << " raw_overlap=" << raw_overlap + << " migration_write=" << migration_write << dendl; + if (raw_overlap == 0) { + image_extents.clear(); + return 0; + } - uint64_t ImageCtx::prune_parent_extents(vector >& objectx, - uint64_t overlap) - { - // drop extents completely beyond the overlap - while (!objectx.empty() && objectx.back().first >= overlap) - objectx.pop_back(); + auto overlap = reduce_parent_overlap(raw_overlap, migration_write); + if (area == overlap.second) { + // drop extents completely beyond the overlap + while (!image_extents.empty() && + image_extents.back().first >= overlap.first) { + image_extents.pop_back(); + } + if (!image_extents.empty()) { + // trim final overlapping extent + auto& last_extent = image_extents.back(); + if (last_extent.first + last_extent.second > overlap.first) { + last_extent.second = overlap.first - last_extent.first; + } + } + } else if (area == io::ImageArea::DATA && + overlap.second == io::ImageArea::CRYPTO_HEADER) { + // all extents completely beyond the overlap + image_extents.clear(); + } else { + // all extents completely within the overlap + ceph_assert(area == io::ImageArea::CRYPTO_HEADER && + overlap.second == io::ImageArea::DATA); + } - // trim final overlapping extent - if (!objectx.empty() && objectx.back().first + objectx.back().second > overlap) - objectx.back().second = overlap - objectx.back().first; + uint64_t overlap_bytes = 0; + for (auto [_, len] : image_extents) { + overlap_bytes += len; + } + ldout(cct, 10) << __func__ << ": overlap=" << overlap.first + << "/" << overlap.second + << " got overlap_bytes=" << overlap_bytes + << " at " << image_extents << dendl; + return overlap_bytes; + } - uint64_t len = 0; - for (vector >::iterator p = objectx.begin(); - p != objectx.end(); - ++p) - len += p->second; - ldout(cct, 10) << "prune_parent_extents image overlap " << overlap - << ", object overlap " << len - << " from image extents " << objectx << dendl; - return len; + void ImageCtx::register_watch(Context *on_finish) { + ceph_assert(image_watcher != NULL); + image_watcher->register_watch(on_finish); } void ImageCtx::cancel_async_requests() { diff --git a/src/librbd/ImageCtx.h b/src/librbd/ImageCtx.h index 8c69615a717..9a432c764d5 100644 --- a/src/librbd/ImageCtx.h +++ b/src/librbd/ImageCtx.h @@ -326,9 +326,11 @@ namespace librbd { uint64_t* raw_overlap) const; std::pair reduce_parent_overlap( uint64_t raw_overlap, bool migration_write) const; + uint64_t prune_parent_extents( + std::vector>& image_extents, + io::ImageArea area, uint64_t raw_overlap, bool migration_write) const; + void register_watch(Context *on_finish); - uint64_t prune_parent_extents(std::vector >& objectx, - uint64_t overlap); void cancel_async_requests(); void cancel_async_requests(Context *on_finish); diff --git a/src/librbd/cache/ObjectCacherWriteback.cc b/src/librbd/cache/ObjectCacherWriteback.cc index 008098f07b0..97f2d46ba85 100644 --- a/src/librbd/cache/ObjectCacherWriteback.cc +++ b/src/librbd/cache/ObjectCacherWriteback.cc @@ -160,19 +160,17 @@ bool ObjectCacherWriteback::may_copy_on_write(const object_t& oid, uint64_t read_len, snapid_t snapid) { - m_ictx->image_lock.lock_shared(); - librados::snap_t snap_id = m_ictx->snap_id; - uint64_t overlap = 0; - m_ictx->get_parent_overlap(snap_id, &overlap); - m_ictx->image_lock.unlock_shared(); - - uint64_t object_no = oid_to_object_no(oid.name, m_ictx->object_prefix); - - // reverse map this object extent onto the parent - auto [parent_extents, _] = io::util::object_to_area_extents( - m_ictx, object_no, {{0, m_ictx->layout.object_size}}); - uint64_t object_overlap = m_ictx->prune_parent_extents(parent_extents, - overlap); + std::shared_lock image_locker(m_ictx->image_lock); + uint64_t raw_overlap = 0; + uint64_t object_overlap = 0; + m_ictx->get_parent_overlap(m_ictx->snap_id, &raw_overlap); + if (raw_overlap > 0) { + uint64_t object_no = oid_to_object_no(oid.name, m_ictx->object_prefix); + auto [parent_extents, area] = io::util::object_to_area_extents( + m_ictx, object_no, {{0, m_ictx->layout.object_size}}); + object_overlap = m_ictx->prune_parent_extents(parent_extents, area, + raw_overlap, false); + } bool may = object_overlap > 0; ldout(m_ictx->cct, 10) << "may_copy_on_write " << oid << " " << read_off << "~" << read_len << " = " << may << dendl; diff --git a/src/librbd/deep_copy/ObjectCopyRequest.cc b/src/librbd/deep_copy/ObjectCopyRequest.cc index 8fa325aaea2..e8b42b68f7d 100644 --- a/src/librbd/deep_copy/ObjectCopyRequest.cc +++ b/src/librbd/deep_copy/ObjectCopyRequest.cc @@ -538,26 +538,21 @@ void ObjectCopyRequest::compute_read_ops() { WriteReadSnapIds write_read_snap_ids{src_snap_seq, src_snap_seq}; // prepare to prune the extents to the maximum parent overlap - m_src_image_ctx->image_lock.lock_shared(); - uint64_t src_parent_overlap = 0; - int r = m_src_image_ctx->get_parent_overlap(src_snap_seq, - &src_parent_overlap); - m_src_image_ctx->image_lock.unlock_shared(); - + std::shared_lock image_locker(m_src_image_ctx->image_lock); + uint64_t raw_overlap = 0; + int r = m_src_image_ctx->get_parent_overlap(src_snap_seq, &raw_overlap); if (r < 0) { ldout(m_cct, 5) << "failed getting parent overlap for snap_id: " << src_snap_seq << ": " << cpp_strerror(r) << dendl; - } else { - ldout(m_cct, 20) << "parent overlap=" << src_parent_overlap << dendl; - for (auto& [image_offset, image_length] : dne_image_interval) { - auto end_image_offset = std::min( - image_offset + image_length, src_parent_overlap); - if (image_offset >= end_image_offset) { - // starting offset is beyond the end of the parent overlap - continue; - } - - image_length = end_image_offset - image_offset; + } else if (raw_overlap > 0) { + ldout(m_cct, 20) << "raw_overlap=" << raw_overlap << dendl; + io::Extents parent_extents; + for (auto [image_offset, image_length] : dne_image_interval) { + parent_extents.emplace_back(image_offset, image_length); + } + m_src_image_ctx->prune_parent_extents(parent_extents, m_image_area, + raw_overlap, false); + for (auto [image_offset, image_length] : parent_extents) { ldout(m_cct, 20) << "parent read op: " << "snap_ids=" << write_read_snap_ids << " " << image_offset << "~" << image_length << dendl; @@ -672,24 +667,20 @@ void ObjectCopyRequest::compute_zero_ops() { if (hide_parent) { std::shared_lock image_locker{m_dst_image_ctx->image_lock}; - uint64_t parent_overlap = 0; - int r = m_dst_image_ctx->get_parent_overlap(dst_snap_seq, - &parent_overlap); + uint64_t raw_overlap = 0; + uint64_t object_overlap = 0; + int r = m_dst_image_ctx->get_parent_overlap(dst_snap_seq, &raw_overlap); if (r < 0) { ldout(m_cct, 5) << "failed getting parent overlap for snap_id: " << dst_snap_seq << ": " << cpp_strerror(r) << dendl; + } else if (raw_overlap > 0) { + auto parent_extents = m_image_extents; + object_overlap = m_dst_image_ctx->prune_parent_extents( + parent_extents, m_image_area, raw_overlap, false); } - if (parent_overlap == 0) { + if (object_overlap == 0) { ldout(m_cct, 20) << "no parent overlap" << dendl; hide_parent = false; - } else { - auto image_extents = m_image_extents; - uint64_t overlap = m_dst_image_ctx->prune_parent_extents( - image_extents, parent_overlap); - if (overlap == 0) { - ldout(m_cct, 20) << "no parent overlap" << dendl; - hide_parent = false; - } } } diff --git a/src/librbd/io/CopyupRequest.cc b/src/librbd/io/CopyupRequest.cc index 220566c64cc..228f9598017 100644 --- a/src/librbd/io/CopyupRequest.cc +++ b/src/librbd/io/CopyupRequest.cc @@ -649,20 +649,19 @@ void CopyupRequest::compute_deep_copy_snap_ids() { return false; } - uint64_t parent_overlap = 0; - int r = m_image_ctx->get_parent_overlap(snap_id, &parent_overlap); + uint64_t raw_overlap = 0; + uint64_t object_overlap = 0; + int r = m_image_ctx->get_parent_overlap(snap_id, &raw_overlap); if (r < 0) { ldout(cct, 5) << "failed getting parent overlap for snap_id: " << snap_id << ": " << cpp_strerror(r) << dendl; + } else if (raw_overlap > 0) { + auto [parent_extents, area] = util::object_to_area_extents( + m_image_ctx, m_object_no, {{0, m_image_ctx->layout.object_size}}); + object_overlap = m_image_ctx->prune_parent_extents(parent_extents, area, + raw_overlap, false); } - if (parent_overlap == 0) { - return false; - } - auto [parent_extents, _] = util::object_to_area_extents( - m_image_ctx, m_object_no, {{0, m_image_ctx->layout.object_size}}); - auto overlap = m_image_ctx->prune_parent_extents(parent_extents, - parent_overlap); - return overlap > 0; + return object_overlap > 0; }); } diff --git a/src/librbd/io/ObjectRequest.cc b/src/librbd/io/ObjectRequest.cc index 9c27be81352..6d246cdf33f 100644 --- a/src/librbd/io/ObjectRequest.cc +++ b/src/librbd/io/ObjectRequest.cc @@ -153,9 +153,9 @@ bool ObjectRequest::compute_parent_extents(Extents *parent_extents, parent_extents->clear(); *area = ImageArea::DATA; - uint64_t parent_overlap; + uint64_t raw_overlap; int r = m_ictx->get_parent_overlap( - m_io_context->read_snap().value_or(CEPH_NOSNAP), &parent_overlap); + m_io_context->read_snap().value_or(CEPH_NOSNAP), &raw_overlap); if (r < 0) { // NOTE: it's possible for a snapshot to be deleted while we are // still reading from it @@ -163,24 +163,20 @@ bool ObjectRequest::compute_parent_extents(Extents *parent_extents, << cpp_strerror(r) << dendl; return false; } - - if (!read_request && !m_ictx->migration_info.empty()) { - parent_overlap = m_ictx->migration_info.overlap; + bool migration_write = !read_request && !m_ictx->migration_info.empty(); + if (migration_write) { + raw_overlap = m_ictx->migration_info.overlap; } - - if (parent_overlap == 0) { + if (raw_overlap == 0) { return false; } std::tie(*parent_extents, *area) = io::util::object_to_area_extents( m_ictx, m_object_no, {{0, m_ictx->layout.object_size}}); - uint64_t object_overlap = m_ictx->prune_parent_extents(*parent_extents, - parent_overlap); + uint64_t object_overlap = m_ictx->prune_parent_extents( + *parent_extents, *area, raw_overlap, migration_write); if (object_overlap > 0) { - ldout(m_ictx->cct, 20) << "overlap=" << parent_overlap - << " extents=" << *parent_extents - << " area=" << *area << dendl; - m_has_parent = !parent_extents->empty(); + m_has_parent = true; return true; } return false; @@ -959,19 +955,17 @@ void ObjectListSnapsRequest::list_from_parent() { return; } - // calculate reverse mapping onto the parent image Extents parent_extents; - std::tie(parent_extents, m_image_area) = io::util::object_to_area_extents( - image_ctx, this->m_object_no, m_object_extents); - - uint64_t parent_overlap = 0; + uint64_t raw_overlap = 0; uint64_t object_overlap = 0; - int r = image_ctx->get_parent_overlap(snap_id_end, &parent_overlap); - if (r == 0) { - object_overlap = image_ctx->prune_parent_extents(parent_extents, - parent_overlap); + image_ctx->get_parent_overlap(snap_id_end, &raw_overlap); + if (raw_overlap > 0) { + // calculate reverse mapping onto the parent image + std::tie(parent_extents, m_image_area) = io::util::object_to_area_extents( + image_ctx, this->m_object_no, m_object_extents); + object_overlap = image_ctx->prune_parent_extents( + parent_extents, m_image_area, raw_overlap, false); } - if (object_overlap == 0) { image_locker.unlock(); diff --git a/src/librbd/io/Utils.cc b/src/librbd/io/Utils.cc index a8cef0e41bb..63d58720613 100644 --- a/src/librbd/io/Utils.cc +++ b/src/librbd/io/Utils.cc @@ -92,22 +92,22 @@ void read_parent(I *image_ctx, uint64_t object_no, ReadExtents* read_extents, std::shared_lock image_locker{image_ctx->image_lock}; - // calculate reverse mapping onto the image - Extents extents; - for (const auto& extent : *read_extents) { - extents.emplace_back(extent.offset, extent.length); - } - auto [parent_extents, area] = object_to_area_extents(image_ctx, object_no, - extents); - - uint64_t parent_overlap = 0; + Extents parent_extents; + ImageArea area; + uint64_t raw_overlap = 0; uint64_t object_overlap = 0; - int r = image_ctx->get_parent_overlap(snap_id, &parent_overlap); - if (r == 0) { - object_overlap = image_ctx->prune_parent_extents(parent_extents, - parent_overlap); + image_ctx->get_parent_overlap(snap_id, &raw_overlap); + if (raw_overlap > 0) { + // calculate reverse mapping onto the parent image + Extents extents; + for (const auto& extent : *read_extents) { + extents.emplace_back(extent.offset, extent.length); + } + std::tie(parent_extents, area) = object_to_area_extents(image_ctx, + object_no, extents); + object_overlap = image_ctx->prune_parent_extents(parent_extents, area, + raw_overlap, false); } - if (object_overlap == 0) { image_locker.unlock(); diff --git a/src/test/librbd/crypto/test_mock_CryptoObjectDispatch.cc b/src/test/librbd/crypto/test_mock_CryptoObjectDispatch.cc index 38a1cbbdebc..bfd29261628 100644 --- a/src/test/librbd/crypto/test_mock_CryptoObjectDispatch.cc +++ b/src/test/librbd/crypto/test_mock_CryptoObjectDispatch.cc @@ -239,7 +239,7 @@ struct TestMockCryptoCryptoObjectDispatch : public TestMockFixture { } void expect_prune_parent_extents(uint64_t object_overlap) { - EXPECT_CALL(*mock_image_ctx, prune_parent_extents(_, _)) + EXPECT_CALL(*mock_image_ctx, prune_parent_extents(_, _, _, _)) .WillOnce(Return(object_overlap)); } diff --git a/src/test/librbd/io/test_mock_CopyupRequest.cc b/src/test/librbd/io/test_mock_CopyupRequest.cc index b8a193619de..a4fe54af211 100644 --- a/src/test/librbd/io/test_mock_CopyupRequest.cc +++ b/src/test/librbd/io/test_mock_CopyupRequest.cc @@ -197,7 +197,7 @@ struct TestMockIoCopyupRequest : public TestMockFixture { void expect_prune_parent_extents(MockTestImageCtx& mock_image_ctx, uint64_t overlap, uint64_t object_overlap) { - EXPECT_CALL(mock_image_ctx, prune_parent_extents(_, overlap)) + EXPECT_CALL(mock_image_ctx, prune_parent_extents(_, _, overlap, _)) .WillOnce(WithoutArgs(Invoke([object_overlap]() { return object_overlap; }))); diff --git a/src/test/librbd/io/test_mock_ObjectRequest.cc b/src/test/librbd/io/test_mock_ObjectRequest.cc index 503c83827d5..0690b7722a0 100644 --- a/src/test/librbd/io/test_mock_ObjectRequest.cc +++ b/src/test/librbd/io/test_mock_ObjectRequest.cc @@ -196,7 +196,7 @@ struct TestMockIoObjectRequest : public TestMockFixture { void expect_prune_parent_extents(MockTestImageCtx &mock_image_ctx, const Extents& extents, uint64_t overlap, uint64_t object_overlap) { - EXPECT_CALL(mock_image_ctx, prune_parent_extents(_, overlap)) + EXPECT_CALL(mock_image_ctx, prune_parent_extents(_, _, overlap, _)) .WillOnce(WithArg<0>(Invoke([extents, object_overlap](Extents& e) { e = extents; return object_overlap; diff --git a/src/test/librbd/mock/MockImageCtx.h b/src/test/librbd/mock/MockImageCtx.h index a4c3fc0326e..8da257f44ab 100644 --- a/src/test/librbd/mock/MockImageCtx.h +++ b/src/test/librbd/mock/MockImageCtx.h @@ -88,8 +88,9 @@ struct MockImageCtx { uint64_t *raw_overlap)); MOCK_CONST_METHOD2(reduce_parent_overlap, std::pair(uint64_t, bool)); - MOCK_CONST_METHOD2(prune_parent_extents, uint64_t(std::vector >& , - uint64_t)); + MOCK_CONST_METHOD4(prune_parent_extents, + uint64_t(std::vector>&, + io::ImageArea, uint64_t, bool)); MOCK_CONST_METHOD2(is_snap_protected, int(librados::snap_t in_snap_id, bool *is_protected)); diff --git a/src/test/librbd/test_librbd.cc b/src/test/librbd/test_librbd.cc index ec31b861b7a..359ea020321 100644 --- a/src/test/librbd/test_librbd.cc +++ b/src/test/librbd/test_librbd.cc @@ -2531,6 +2531,158 @@ TEST_F(TestLibRBD, TestCloneEncryption) rados_ioctx_destroy(ioctx); } +TEST_F(TestLibRBD, LUKS1UnderLUKS2WithoutResize) +{ + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + REQUIRE(!is_feature_enabled(RBD_FEATURE_STRIPINGV2)); + REQUIRE(!is_feature_enabled(RBD_FEATURE_JOURNALING)); + + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + librbd::RBD rbd; + std::string parent_name = get_temp_image_name(); + std::string clone_name = get_temp_image_name(); + uint64_t data_size = 25 << 20; + uint64_t luks1_meta_size = 4 << 20; + uint64_t luks2_meta_size = 16 << 20; + std::string parent_passphrase = "parent passphrase"; + std::string clone_passphrase = "clone passphrase"; + + { + int order = 22; + ASSERT_EQ(0, create_image_pp(rbd, ioctx, parent_name.c_str(), + luks1_meta_size + data_size, &order)); + librbd::Image parent; + ASSERT_EQ(0, rbd.open(ioctx, parent, parent_name.c_str(), nullptr)); + + librbd::encryption_luks1_format_options_t fopts = { + RBD_ENCRYPTION_ALGORITHM_AES256, parent_passphrase}; + ASSERT_EQ(0, parent.encryption_format(RBD_ENCRYPTION_FORMAT_LUKS1, &fopts, + sizeof(fopts))); + + ceph::bufferlist bl; + bl.append(std::string(data_size, 'a')); + ASSERT_EQ(data_size, parent.write(0, data_size, bl)); + + ASSERT_EQ(0, parent.snap_create("snap")); + ASSERT_EQ(0, parent.snap_protect("snap")); + uint64_t features; + ASSERT_EQ(0, parent.features(&features)); + ASSERT_EQ(0, rbd.clone(ioctx, parent_name.c_str(), "snap", ioctx, + clone_name.c_str(), features, &order)); + } + + { + librbd::Image clone; + ASSERT_EQ(0, rbd.open(ioctx, clone, clone_name.c_str(), nullptr)); + + librbd::encryption_luks2_format_options_t fopts = + {RBD_ENCRYPTION_ALGORITHM_AES256, clone_passphrase}; + ASSERT_EQ(0, clone.encryption_format(RBD_ENCRYPTION_FORMAT_LUKS2, &fopts, + sizeof(fopts))); + + librbd::encryption_luks_format_options_t opts1 = {parent_passphrase}; + librbd::encryption_luks_format_options_t opts2 = {clone_passphrase}; + librbd::encryption_spec_t specs[] = { + {RBD_ENCRYPTION_FORMAT_LUKS, &opts2, sizeof(opts2)}, + {RBD_ENCRYPTION_FORMAT_LUKS, &opts1, sizeof(opts1)}}; + ASSERT_EQ(0, clone.encryption_load2(specs, std::size(specs))); + + uint64_t size; + ASSERT_EQ(0, clone.size(&size)); + EXPECT_EQ(data_size + luks1_meta_size - luks2_meta_size, size); + uint64_t overlap; + ASSERT_EQ(0, clone.overlap(&overlap)); + EXPECT_EQ(data_size + luks1_meta_size - luks2_meta_size, overlap); + + ceph::bufferlist expected_bl; + expected_bl.append(std::string( + data_size + luks1_meta_size - luks2_meta_size, 'a')); + + ceph::bufferlist read_bl; + ASSERT_EQ(expected_bl.length(), + clone.read(0, expected_bl.length(), read_bl)); + EXPECT_TRUE(expected_bl.contents_equal(read_bl)); + } +} + +TEST_F(TestLibRBD, LUKS2UnderLUKS1WithoutResize) +{ + REQUIRE_FEATURE(RBD_FEATURE_LAYERING); + REQUIRE(!is_feature_enabled(RBD_FEATURE_STRIPINGV2)); + REQUIRE(!is_feature_enabled(RBD_FEATURE_JOURNALING)); + + librados::IoCtx ioctx; + ASSERT_EQ(0, _rados.ioctx_create(m_pool_name.c_str(), ioctx)); + + librbd::RBD rbd; + std::string parent_name = get_temp_image_name(); + std::string clone_name = get_temp_image_name(); + uint64_t data_size = 25 << 20; + uint64_t luks1_meta_size = 4 << 20; + uint64_t luks2_meta_size = 16 << 20; + std::string parent_passphrase = "parent passphrase"; + std::string clone_passphrase = "clone passphrase"; + + { + int order = 22; + ASSERT_EQ(0, create_image_pp(rbd, ioctx, parent_name.c_str(), + luks2_meta_size + data_size, &order)); + librbd::Image parent; + ASSERT_EQ(0, rbd.open(ioctx, parent, parent_name.c_str(), nullptr)); + + librbd::encryption_luks2_format_options_t fopts = { + RBD_ENCRYPTION_ALGORITHM_AES256, parent_passphrase}; + ASSERT_EQ(0, parent.encryption_format(RBD_ENCRYPTION_FORMAT_LUKS2, &fopts, + sizeof(fopts))); + + ceph::bufferlist bl; + bl.append(std::string(data_size, 'a')); + ASSERT_EQ(data_size, parent.write(0, data_size, bl)); + + ASSERT_EQ(0, parent.snap_create("snap")); + ASSERT_EQ(0, parent.snap_protect("snap")); + uint64_t features; + ASSERT_EQ(0, parent.features(&features)); + ASSERT_EQ(0, rbd.clone(ioctx, parent_name.c_str(), "snap", ioctx, + clone_name.c_str(), features, &order)); + } + + { + librbd::Image clone; + ASSERT_EQ(0, rbd.open(ioctx, clone, clone_name.c_str(), nullptr)); + + librbd::encryption_luks1_format_options_t fopts = + {RBD_ENCRYPTION_ALGORITHM_AES256, clone_passphrase}; + ASSERT_EQ(0, clone.encryption_format(RBD_ENCRYPTION_FORMAT_LUKS1, &fopts, + sizeof(fopts))); + + librbd::encryption_luks_format_options_t opts1 = {parent_passphrase}; + librbd::encryption_luks_format_options_t opts2 = {clone_passphrase}; + librbd::encryption_spec_t specs[] = { + {RBD_ENCRYPTION_FORMAT_LUKS, &opts2, sizeof(opts2)}, + {RBD_ENCRYPTION_FORMAT_LUKS, &opts1, sizeof(opts1)}}; + ASSERT_EQ(0, clone.encryption_load2(specs, std::size(specs))); + + uint64_t size; + ASSERT_EQ(0, clone.size(&size)); + EXPECT_EQ(data_size + luks2_meta_size - luks1_meta_size, size); + uint64_t overlap; + ASSERT_EQ(0, clone.overlap(&overlap)); + EXPECT_EQ(data_size, overlap); + + ceph::bufferlist expected_bl; + expected_bl.append(std::string(data_size, 'a')); + expected_bl.append_zero(luks2_meta_size - luks1_meta_size); + + ceph::bufferlist read_bl; + ASSERT_EQ(expected_bl.length(), + clone.read(0, expected_bl.length(), read_bl)); + EXPECT_TRUE(expected_bl.contents_equal(read_bl)); + } +} + TEST_F(TestLibRBD, EncryptionFormatNoData) { REQUIRE(!is_feature_enabled(RBD_FEATURE_JOURNALING));