From 75b982ad30d8e768ed75fc315d7154e782e00b0c Mon Sep 17 00:00:00 2001 From: David Anderson Date: Mon, 21 Sep 2020 16:34:25 -0700 Subject: [PATCH] libsnapshot: Partially implement OpenSnapshotWriter. Implement OpenSnapshotWriter for non-compressed Virtual A/B. This is done by adding an OnlineKernelSnapshotWriter class, which forwards all writes to a dm-snapshot block device. This also introduces a new ISnapshotWriter class which extends ICowWriter, and adds features specific to libsnapshot (versus ICowWriter which is intended only for the new COW format). The OpenSnapshotReader call has been moved here since the writer retains all the information needed to create the reader. To test the new call, vts_libsnapshot_test has been modified to use OpenSnapshotWriter. As part of this change, all consumers of libsnapshot must now link to libsnapshot_cow. Bug: 168554689 Test: vts_libsnapshot_test Change-Id: Ieedfadc557833c1e0540922aabc6e95c80266a64 --- fastboot/Android.bp | 1 + fs_mgr/libsnapshot/Android.bp | 4 + fs_mgr/libsnapshot/cow_writer.cpp | 9 +- .../include/libsnapshot/cow_writer.h | 3 + .../include/libsnapshot/mock_snapshot.h | 4 +- .../include/libsnapshot/snapshot.h | 59 ++++++---- .../include/libsnapshot/snapshot_stub.h | 4 +- .../include/libsnapshot/snapshot_writer.h | 68 +++++++++++ .../include_test/libsnapshot/test_helpers.h | 1 + fs_mgr/libsnapshot/snapshot.cpp | 111 +++++++++++++++--- fs_mgr/libsnapshot/snapshot_stub.cpp | 8 +- fs_mgr/libsnapshot/snapshot_test.cpp | 60 +++++----- fs_mgr/libsnapshot/snapshot_writer.cpp | 91 ++++++++++++++ fs_mgr/libsnapshot/test_helpers.cpp | 42 +++++++ init/Android.bp | 1 + init/Android.mk | 1 + 16 files changed, 385 insertions(+), 82 deletions(-) create mode 100644 fs_mgr/libsnapshot/include/libsnapshot/snapshot_writer.h create mode 100644 fs_mgr/libsnapshot/snapshot_writer.cpp diff --git a/fastboot/Android.bp b/fastboot/Android.bp index 81ebf4392..e6f9ffaee 100644 --- a/fastboot/Android.bp +++ b/fastboot/Android.bp @@ -147,6 +147,7 @@ cc_binary { static_libs: [ "libgtest_prod", "libhealthhalutils", + "libsnapshot_cow", "libsnapshot_nobinder", "update_metadata-protos", ], diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp index aa41be3d0..a3384e406 100644 --- a/fs_mgr/libsnapshot/Android.bp +++ b/fs_mgr/libsnapshot/Android.bp @@ -77,6 +77,7 @@ filegroup { "snapshot_stats.cpp", "snapshot_stub.cpp", "snapshot_metadata_updater.cpp", + "snapshot_writer.cpp", "partition_cow_creator.cpp", "return.cpp", "utility.cpp", @@ -215,6 +216,7 @@ cc_defaults { "libgmock", "liblp", "libsnapshot", + "libsnapshot_cow", "libsnapshot_test_helpers", "libsparse", ], @@ -249,6 +251,7 @@ cc_binary { static_libs: [ "libfstab", "libsnapshot", + "libsnapshot_cow", "update_metadata-protos", ], shared_libs: [ @@ -312,6 +315,7 @@ cc_defaults { "libgmock", // from libsnapshot_test_helpers "liblog", "liblp", + "libsnapshot_cow", "libsnapshot_test_helpers", "libprotobuf-mutator", ], diff --git a/fs_mgr/libsnapshot/cow_writer.cpp b/fs_mgr/libsnapshot/cow_writer.cpp index 70fdac197..f96f174c5 100644 --- a/fs_mgr/libsnapshot/cow_writer.cpp +++ b/fs_mgr/libsnapshot/cow_writer.cpp @@ -32,6 +32,9 @@ namespace snapshot { static_assert(sizeof(off_t) == sizeof(uint64_t)); +using android::base::borrowed_fd; +using android::base::unique_fd; + bool ICowWriter::AddCopy(uint64_t new_block, uint64_t old_block) { if (!ValidateNewBlock(new_block)) { return false; @@ -98,12 +101,12 @@ bool CowWriter::ParseOptions() { return true; } -bool CowWriter::Initialize(android::base::unique_fd&& fd, OpenMode mode) { +bool CowWriter::Initialize(unique_fd&& fd, OpenMode mode) { owned_fd_ = std::move(fd); - return Initialize(android::base::borrowed_fd{owned_fd_}, mode); + return Initialize(borrowed_fd{owned_fd_}, mode); } -bool CowWriter::Initialize(android::base::borrowed_fd fd, OpenMode mode) { +bool CowWriter::Initialize(borrowed_fd fd, OpenMode mode) { fd_ = fd; if (!ParseOptions()) { diff --git a/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h b/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h index 245da0c7d..2bc017100 100644 --- a/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h +++ b/fs_mgr/libsnapshot/include/libsnapshot/cow_writer.h @@ -58,6 +58,9 @@ class ICowWriter { // Return number of bytes the cow image occupies on disk. virtual uint64_t GetCowSize() = 0; + // Returns true if AddCopy() operations are supported. + virtual bool SupportsCopyOperation() const { return true; } + const CowOptions& options() { return options_; } protected: diff --git a/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h index eb6ad0537..13f19aa87 100644 --- a/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h +++ b/fs_mgr/libsnapshot/include/libsnapshot/mock_snapshot.h @@ -38,9 +38,7 @@ class MockSnapshotManager : public ISnapshotManager { (const android::fs_mgr::CreateLogicalPartitionParams& params, std::string* snapshot_path), (override)); - MOCK_METHOD(std::unique_ptr, OpenSnapshotWriter, - (const android::fs_mgr::CreateLogicalPartitionParams& params), (override)); - MOCK_METHOD(std::unique_ptr, OpenSnapshotReader, + MOCK_METHOD(std::unique_ptr, OpenSnapshotWriter, (const android::fs_mgr::CreateLogicalPartitionParams& params), (override)); MOCK_METHOD(bool, UnmapUpdateSnapshot, (const std::string& target_partition_name), (override)); MOCK_METHOD(bool, NeedSnapshotsInFirstStageMount, (), (override)); diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h index 6fef58a7f..1bc972e9a 100644 --- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h +++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot.h @@ -35,8 +35,8 @@ #include #include -#include #include +#include #ifndef FRIEND_TEST #define FRIEND_TEST(test_set_name, individual_test) \ @@ -44,10 +44,6 @@ #define DEFINED_FRIEND_TEST #endif -namespace chromeos_update_engine { -class FileDescriptor; -} // namespace chromeos_update_engine - namespace android { namespace fiemap { @@ -110,8 +106,6 @@ class ISnapshotManager { }; virtual ~ISnapshotManager() = default; - using FileDescriptor = chromeos_update_engine::FileDescriptor; - // Begin an update. This must be called before creating any snapshots. It // will fail if GetUpdateState() != None. virtual bool BeginUpdate() = 0; @@ -187,19 +181,14 @@ class ISnapshotManager { virtual bool MapUpdateSnapshot(const android::fs_mgr::CreateLogicalPartitionParams& params, std::string* snapshot_path) = 0; - // Create an ICowWriter to build a snapshot against a target partition. The partition name must - // be suffixed. - virtual std::unique_ptr OpenSnapshotWriter( - const android::fs_mgr::CreateLogicalPartitionParams& params) = 0; - - // Open a snapshot for reading. A file-like interface is provided through the FileDescriptor. - // In this mode, writes are not supported. The partition name must be suffixed. - virtual std::unique_ptr OpenSnapshotReader( + // Create an ISnapshotWriter to build a snapshot against a target partition. The partition name + // must be suffixed. + virtual std::unique_ptr OpenSnapshotWriter( const android::fs_mgr::CreateLogicalPartitionParams& params) = 0; // Unmap a snapshot device or CowWriter that was previously opened with MapUpdateSnapshot, - // OpenSnapshotWriter, or OpenSnapshotReader. All outstanding open descriptors, writers, - // or readers must be deleted before this is called. + // OpenSnapshotWriter. All outstanding open descriptors, writers, or + // readers must be deleted before this is called. virtual bool UnmapUpdateSnapshot(const std::string& target_partition_name) = 0; // If this returns true, first-stage mount must call @@ -310,9 +299,7 @@ class SnapshotManager final : public ISnapshotManager { Return CreateUpdateSnapshots(const DeltaArchiveManifest& manifest) override; bool MapUpdateSnapshot(const CreateLogicalPartitionParams& params, std::string* snapshot_path) override; - std::unique_ptr OpenSnapshotWriter( - const android::fs_mgr::CreateLogicalPartitionParams& params) override; - std::unique_ptr OpenSnapshotReader( + std::unique_ptr OpenSnapshotWriter( const android::fs_mgr::CreateLogicalPartitionParams& params) override; bool UnmapUpdateSnapshot(const std::string& target_partition_name) override; bool NeedSnapshotsInFirstStageMount() override; @@ -532,9 +519,39 @@ class SnapshotManager final : public ISnapshotManager { std::string GetSnapshotDeviceName(const std::string& snapshot_name, const SnapshotStatus& status); + // Reason for calling MapPartitionWithSnapshot. + enum class SnapshotContext { + // For writing or verification (during update_engine). + Update, + + // For mounting a full readable device. + Mount, + }; + + struct SnapshotPaths { + // Target/base device (eg system_b), always present. + std::string target_device; + + // COW path (eg system_cow). Not present if no COW is needed. + std::string cow_device; + + // dm-snapshot instance. Not present in Update mode for VABC. + std::string snapshot_device; + }; + + // Helpers for OpenSnapshotWriter. + std::unique_ptr OpenCompressedSnapshotWriter(LockedFile* lock, + const std::string& partition_name, + const SnapshotStatus& status, + const SnapshotPaths& paths); + std::unique_ptr OpenKernelSnapshotWriter(LockedFile* lock, + const std::string& partition_name, + const SnapshotStatus& status, + const SnapshotPaths& paths); + // Map the base device, COW devices, and snapshot device. bool MapPartitionWithSnapshot(LockedFile* lock, CreateLogicalPartitionParams params, - std::string* path); + SnapshotContext context, SnapshotPaths* paths); // Map the COW devices, including the partition in super and the images. // |params|: diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h index 149f463a9..cda2bee33 100644 --- a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h +++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_stub.h @@ -36,9 +36,7 @@ class SnapshotManagerStub : public ISnapshotManager { const chromeos_update_engine::DeltaArchiveManifest& manifest) override; bool MapUpdateSnapshot(const android::fs_mgr::CreateLogicalPartitionParams& params, std::string* snapshot_path) override; - std::unique_ptr OpenSnapshotWriter( - const android::fs_mgr::CreateLogicalPartitionParams& params) override; - std::unique_ptr OpenSnapshotReader( + std::unique_ptr OpenSnapshotWriter( const android::fs_mgr::CreateLogicalPartitionParams& params) override; bool UnmapUpdateSnapshot(const std::string& target_partition_name) override; bool NeedSnapshotsInFirstStageMount() override; diff --git a/fs_mgr/libsnapshot/include/libsnapshot/snapshot_writer.h b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_writer.h new file mode 100644 index 000000000..bf57a0078 --- /dev/null +++ b/fs_mgr/libsnapshot/include/libsnapshot/snapshot_writer.h @@ -0,0 +1,68 @@ +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#pragma once + +#include + +#include + +namespace chromeos_update_engine { +class FileDescriptor; +} // namespace chromeos_update_engine + +namespace android { +namespace snapshot { + +class ISnapshotWriter : public ICowWriter { + public: + using FileDescriptor = chromeos_update_engine::FileDescriptor; + + explicit ISnapshotWriter(const CowOptions& options); + + // Set the source device. This is used for AddCopy() operations, if the + // underlying writer needs the original bytes (for example if backed by + // dm-snapshot or if writing directly to an unsnapshotted region). + void SetSourceDevice(android::base::unique_fd&& source_fd); + + virtual std::unique_ptr OpenReader() = 0; + + protected: + android::base::unique_fd source_fd_; +}; + +// Write directly to a dm-snapshot device. +class OnlineKernelSnapshotWriter : public ISnapshotWriter { + public: + OnlineKernelSnapshotWriter(const CowOptions& options); + + // Set the device used for all writes. + void SetSnapshotDevice(android::base::unique_fd&& snapshot_fd, uint64_t cow_size); + + bool Flush() override; + uint64_t GetCowSize() override { return cow_size_; } + virtual std::unique_ptr OpenReader() override; + + protected: + bool EmitRawBlocks(uint64_t new_block_start, const void* data, size_t size) override; + bool EmitZeroBlocks(uint64_t new_block_start, uint64_t num_blocks) override; + bool EmitCopy(uint64_t new_block, uint64_t old_block) override; + + private: + android::base::unique_fd snapshot_fd_; + uint64_t cow_size_ = 0; +}; + +} // namespace snapshot +} // namespace android diff --git a/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h b/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h index 8e369b052..197aeaa05 100644 --- a/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h +++ b/fs_mgr/libsnapshot/include_test/libsnapshot/test_helpers.h @@ -144,6 +144,7 @@ void DeleteBackingImage(android::fiemap::IImageManager* manager, const std::stri // Expect space of |path| is multiple of 4K. bool WriteRandomData(const std::string& path, std::optional expect_size = std::nullopt, std::string* hash = nullptr); +bool WriteRandomData(ICowWriter* writer, std::string* hash = nullptr); std::optional GetHash(const std::string& path); diff --git a/fs_mgr/libsnapshot/snapshot.cpp b/fs_mgr/libsnapshot/snapshot.cpp index 0904fc76b..8ac2f830a 100644 --- a/fs_mgr/libsnapshot/snapshot.cpp +++ b/fs_mgr/libsnapshot/snapshot.cpp @@ -15,6 +15,7 @@ #include #include +#include #include #include #include @@ -1570,7 +1571,8 @@ bool SnapshotManager::CreateLogicalAndSnapshotPartitions( .timeout_ms = timeout_ms, }; std::string ignore_path; - if (!MapPartitionWithSnapshot(lock.get(), std::move(params), &ignore_path)) { + if (!MapPartitionWithSnapshot(lock.get(), std::move(params), SnapshotContext::Mount, + nullptr)) { return false; } } @@ -1598,11 +1600,10 @@ static std::chrono::milliseconds GetRemainingTime( bool SnapshotManager::MapPartitionWithSnapshot(LockedFile* lock, CreateLogicalPartitionParams params, - std::string* path) { + SnapshotContext context, SnapshotPaths* paths) { auto begin = std::chrono::steady_clock::now(); CHECK(lock); - path->clear(); if (params.GetPartitionName() != params.GetDeviceName()) { LOG(ERROR) << "Mapping snapshot with a different name is unsupported: partition_name = " @@ -1683,8 +1684,11 @@ bool SnapshotManager::MapPartitionWithSnapshot(LockedFile* lock, } created_devices.EmplaceBack(&dm, params.GetDeviceName()); + if (paths) { + paths->target_device = base_path; + } + if (!live_snapshot_status.has_value()) { - *path = base_path; created_devices.Release(); return true; } @@ -1711,21 +1715,33 @@ bool SnapshotManager::MapPartitionWithSnapshot(LockedFile* lock, LOG(ERROR) << "Could not determine major/minor for: " << cow_name; return false; } + if (paths) { + paths->cow_device = cow_device; + } remaining_time = GetRemainingTime(params.timeout_ms, begin); if (remaining_time.count() < 0) return false; + if (context == SnapshotContext::Update && IsCompressionEnabled()) { + // Stop here, we can't run dm-user yet, the COW isn't built. + return true; + } + + std::string path; if (!MapSnapshot(lock, params.GetPartitionName(), base_device, cow_device, remaining_time, - path)) { + &path)) { LOG(ERROR) << "Could not map snapshot for partition: " << params.GetPartitionName(); return false; } // No need to add params.GetPartitionName() to created_devices since it is immediately released. + if (paths) { + paths->snapshot_device = path; + } + created_devices.Release(); - LOG(INFO) << "Mapped " << params.GetPartitionName() << " as snapshot device at " << *path; - + LOG(INFO) << "Mapped " << params.GetPartitionName() << " as snapshot device at " << path; return true; } @@ -2438,23 +2454,86 @@ bool SnapshotManager::MapUpdateSnapshot(const CreateLogicalPartitionParams& para << params.GetPartitionName(); return false; } - return MapPartitionWithSnapshot(lock.get(), params, snapshot_path); + + SnapshotPaths paths; + if (!MapPartitionWithSnapshot(lock.get(), params, SnapshotContext::Update, &paths)) { + return false; + } + + if (paths.snapshot_device.empty()) { + *snapshot_path = paths.snapshot_device; + } else { + *snapshot_path = paths.target_device; + } + return true; } -std::unique_ptr SnapshotManager::OpenSnapshotWriter( +std::unique_ptr SnapshotManager::OpenSnapshotWriter( const android::fs_mgr::CreateLogicalPartitionParams& params) { - (void)params; + // First unmap any existing mapping. + auto lock = LockShared(); + if (!lock) return nullptr; + if (!UnmapPartitionWithSnapshot(lock.get(), params.GetPartitionName())) { + LOG(ERROR) << "Cannot unmap existing snapshot before re-mapping it: " + << params.GetPartitionName(); + return nullptr; + } - LOG(ERROR) << "OpenSnapshotWriter not yet implemented"; + SnapshotPaths paths; + if (!MapPartitionWithSnapshot(lock.get(), params, SnapshotContext::Update, &paths)) { + return nullptr; + } + + SnapshotStatus status; + if (!paths.cow_device.empty()) { + if (!ReadSnapshotStatus(lock.get(), params.GetPartitionName(), &status)) { + return nullptr; + } + } else { + // Currently, partition_cow_creator always creates snapshots. The + // reason is that if partition X shrinks while partition Y grows, we + // cannot bindly write to the newly freed extents in X. This would + // make the old slot unusable. So, the entire size of the target + // partition is currently considered snapshottable. + LOG(ERROR) << "No snapshot available for partition " << params.GetPartitionName(); + return nullptr; + } + + if (IsCompressionEnabled()) { + return OpenCompressedSnapshotWriter(lock.get(), params.GetPartitionName(), status, paths); + } else { + return OpenKernelSnapshotWriter(lock.get(), params.GetPartitionName(), status, paths); + } +} + +std::unique_ptr SnapshotManager::OpenCompressedSnapshotWriter( + LockedFile*, const std::string&, const SnapshotStatus&, const SnapshotPaths&) { + LOG(ERROR) << "OpenSnapshotWriter not yet implemented for compression"; return nullptr; } -std::unique_ptr SnapshotManager::OpenSnapshotReader( - const android::fs_mgr::CreateLogicalPartitionParams& params) { - (void)params; +std::unique_ptr SnapshotManager::OpenKernelSnapshotWriter( + LockedFile* lock, [[maybe_unused]] const std::string& partition_name, + const SnapshotStatus& status, const SnapshotPaths& paths) { + CHECK(lock); - LOG(ERROR) << "OpenSnapshotReader not yet implemented"; - return nullptr; + CowOptions cow_options; + cow_options.max_blocks = {status.device_size() / cow_options.block_size}; + + auto writer = std::make_unique(cow_options); + + std::string_view path = + paths.snapshot_device.empty() ? paths.target_device : paths.snapshot_device; + unique_fd fd(open(path.data(), O_RDWR | O_CLOEXEC)); + if (fd < 0) { + PLOG(ERROR) << "open failed: " << path; + return nullptr; + } + + uint64_t cow_size = status.cow_partition_size() + status.cow_file_size(); + writer->SetSnapshotDevice(std::move(fd), cow_size); + + return writer; } bool SnapshotManager::UnmapUpdateSnapshot(const std::string& target_partition_name) { diff --git a/fs_mgr/libsnapshot/snapshot_stub.cpp b/fs_mgr/libsnapshot/snapshot_stub.cpp index 8ae63054f..41f5da4a8 100644 --- a/fs_mgr/libsnapshot/snapshot_stub.cpp +++ b/fs_mgr/libsnapshot/snapshot_stub.cpp @@ -130,13 +130,7 @@ ISnapshotMergeStats* SnapshotManagerStub::GetSnapshotMergeStatsInstance() { return &snapshot_merge_stats; } -std::unique_ptr SnapshotManagerStub::OpenSnapshotWriter( - const CreateLogicalPartitionParams&) { - LOG(ERROR) << __FUNCTION__ << " should never be called."; - return nullptr; -} - -std::unique_ptr SnapshotManagerStub::OpenSnapshotReader( +std::unique_ptr SnapshotManagerStub::OpenSnapshotWriter( const CreateLogicalPartitionParams&) { LOG(ERROR) << __FUNCTION__ << " should never be called."; return nullptr; diff --git a/fs_mgr/libsnapshot/snapshot_test.cpp b/fs_mgr/libsnapshot/snapshot_test.cpp index 6ff935bac..f2caaa4f3 100644 --- a/fs_mgr/libsnapshot/snapshot_test.cpp +++ b/fs_mgr/libsnapshot/snapshot_test.cpp @@ -80,6 +80,7 @@ TestDeviceInfo* test_device = nullptr; std::string fake_super; void MountMetadata(); +bool IsCompressionEnabled(); class SnapshotTest : public ::testing::Test { public: @@ -892,42 +893,39 @@ class SnapshotUpdateTest : public SnapshotTest { return AssertionSuccess(); } - AssertionResult MapUpdateSnapshot(const std::string& name, std::string* path = nullptr) { - std::string real_path; - if (!sm->MapUpdateSnapshot( - CreateLogicalPartitionParams{ - .block_device = fake_super, - .metadata_slot = 1, - .partition_name = name, - .timeout_ms = 10s, - .partition_opener = opener_.get(), - }, - &real_path)) { - return AssertionFailure() << "Unable to map snapshot " << name; + AssertionResult MapUpdateSnapshot(const std::string& name, + std::unique_ptr* writer = nullptr) { + CreateLogicalPartitionParams params{ + .block_device = fake_super, + .metadata_slot = 1, + .partition_name = name, + .timeout_ms = 10s, + .partition_opener = opener_.get(), + }; + + auto result = sm->OpenSnapshotWriter(params); + if (!result) { + return AssertionFailure() << "Cannot open snapshot for writing: " << name; } - if (path) { - *path = real_path; + + if (writer) { + *writer = std::move(result); } - return AssertionSuccess() << "Mapped snapshot " << name << " to " << real_path; + return AssertionSuccess(); } - AssertionResult WriteSnapshotAndHash(const std::string& name, - std::optional size = std::nullopt) { - std::string path; - auto res = MapUpdateSnapshot(name, &path); + AssertionResult WriteSnapshotAndHash(const std::string& name) { + std::unique_ptr writer; + auto res = MapUpdateSnapshot(name, &writer); if (!res) { return res; } - std::string size_string = size ? (std::to_string(*size) + " bytes") : ""; - - if (!WriteRandomData(path, size, &hashes_[name])) { - return AssertionFailure() << "Unable to write " << size_string << " to " << path - << " for partition " << name; + if (!WriteRandomData(writer.get(), &hashes_[name])) { + return AssertionFailure() << "Unable to write random data to snapshot " << name; } - return AssertionSuccess() << "Written " << size_string << " to " << path - << " for snapshot partition " << name + return AssertionSuccess() << "Written random data to snapshot " << name << ", hash: " << hashes_[name]; } @@ -1003,7 +1001,7 @@ TEST_F(SnapshotUpdateTest, FullUpdateFlow) { // Write some data to target partitions. for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) { - ASSERT_TRUE(WriteSnapshotAndHash(name, partition_size)); + ASSERT_TRUE(WriteSnapshotAndHash(name)); } // Assert that source partitions aren't affected. @@ -1406,6 +1404,10 @@ void MountMetadata() { MetadataMountedTest().TearDown(); } +bool IsCompressionEnabled() { + return android::base::GetBoolProperty("ro.virtual_ab.compression.enabled", false); +} + TEST_F(MetadataMountedTest, Android) { auto device = sm->EnsureMetadataMounted(); EXPECT_NE(nullptr, device); @@ -1623,7 +1625,7 @@ TEST_F(SnapshotUpdateTest, Hashtree) { // Map and write some data to target partition. ASSERT_TRUE(MapUpdateSnapshots({"vnd_b", "prd_b"})); - ASSERT_TRUE(WriteSnapshotAndHash("sys_b", partition_size)); + ASSERT_TRUE(WriteSnapshotAndHash("sys_b")); // Finish update. ASSERT_TRUE(sm->FinishedSnapshotWrites(false)); @@ -1655,7 +1657,7 @@ TEST_F(SnapshotUpdateTest, Overflow) { // Map and write some data to target partitions. ASSERT_TRUE(MapUpdateSnapshots({"vnd_b", "prd_b"})); - ASSERT_TRUE(WriteSnapshotAndHash("sys_b", actual_write_size)); + ASSERT_TRUE(WriteSnapshotAndHash("sys_b")); std::vector table; ASSERT_TRUE(DeviceMapper::Instance().GetTableStatus("sys_b", &table)); diff --git a/fs_mgr/libsnapshot/snapshot_writer.cpp b/fs_mgr/libsnapshot/snapshot_writer.cpp new file mode 100644 index 000000000..1958f188b --- /dev/null +++ b/fs_mgr/libsnapshot/snapshot_writer.cpp @@ -0,0 +1,91 @@ +// +// Copyright (C) 2020 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include + +#include +#include +#include + +namespace android { +namespace snapshot { + +using chromeos_update_engine::FileDescriptor; + +ISnapshotWriter::ISnapshotWriter(const CowOptions& options) : ICowWriter(options) {} + +void ISnapshotWriter::SetSourceDevice(android::base::unique_fd&& source_fd) { + source_fd_ = std::move(source_fd); +} + +OnlineKernelSnapshotWriter::OnlineKernelSnapshotWriter(const CowOptions& options) + : ISnapshotWriter(options) {} + +void OnlineKernelSnapshotWriter::SetSnapshotDevice(android::base::unique_fd&& snapshot_fd, + uint64_t cow_size) { + snapshot_fd_ = std::move(snapshot_fd); + cow_size_ = cow_size; +} + +bool OnlineKernelSnapshotWriter::Flush() { + if (fsync(snapshot_fd_.get()) < 0) { + PLOG(ERROR) << "fsync"; + return false; + } + return true; +} + +bool OnlineKernelSnapshotWriter::EmitRawBlocks(uint64_t new_block_start, const void* data, + size_t size) { + uint64_t offset = new_block_start * options_.block_size; + if (lseek(snapshot_fd_.get(), offset, SEEK_SET) < 0) { + PLOG(ERROR) << "EmitRawBlocks lseek to offset " << offset; + return false; + } + if (!android::base::WriteFully(snapshot_fd_, data, size)) { + PLOG(ERROR) << "EmitRawBlocks write"; + return false; + } + return true; +} + +bool OnlineKernelSnapshotWriter::EmitZeroBlocks(uint64_t new_block_start, uint64_t num_blocks) { + std::string zeroes(options_.block_size, 0); + for (uint64_t i = 0; i < num_blocks; i++) { + if (!EmitRawBlocks(new_block_start + i, zeroes.data(), zeroes.size())) { + return false; + } + } + return true; +} + +bool OnlineKernelSnapshotWriter::EmitCopy(uint64_t new_block, uint64_t old_block) { + std::string buffer(options_.block_size, 0); + uint64_t offset = old_block * options_.block_size; + if (!android::base::ReadFullyAtOffset(source_fd_, buffer.data(), buffer.size(), offset)) { + PLOG(ERROR) << "EmitCopy read"; + return false; + } + return EmitRawBlocks(new_block, buffer.data(), buffer.size()); +} + +std::unique_ptr OnlineKernelSnapshotWriter::OpenReader() { + LOG(ERROR) << "OnlineKernelSnapshotWriter::OpenReader not yet implemented"; + return nullptr; +} + +} // namespace snapshot +} // namespace android diff --git a/fs_mgr/libsnapshot/test_helpers.cpp b/fs_mgr/libsnapshot/test_helpers.cpp index b07bf9182..6104c8243 100644 --- a/fs_mgr/libsnapshot/test_helpers.cpp +++ b/fs_mgr/libsnapshot/test_helpers.cpp @@ -127,6 +127,48 @@ bool WriteRandomData(const std::string& path, std::optional expect_size, return true; } +bool WriteRandomData(ICowWriter* writer, std::string* hash) { + unique_fd rand(open("/dev/urandom", O_RDONLY)); + if (rand < 0) { + PLOG(ERROR) << "open /dev/urandom"; + return false; + } + + SHA256_CTX ctx; + if (hash) { + SHA256_Init(&ctx); + } + + if (!writer->options().max_blocks) { + LOG(ERROR) << "CowWriter must specify maximum number of blocks"; + return false; + } + uint64_t num_blocks = writer->options().max_blocks.value(); + + size_t block_size = writer->options().block_size; + std::string block(block_size, '\0'); + for (uint64_t i = 0; i < num_blocks; i++) { + if (!ReadFully(rand, block.data(), block.size())) { + PLOG(ERROR) << "read /dev/urandom"; + return false; + } + if (!writer->AddRawBlocks(i, block.data(), block.size())) { + LOG(ERROR) << "Failed to add raw block " << i; + return false; + } + if (hash) { + SHA256_Update(&ctx, block.data(), block.size()); + } + } + + if (hash) { + uint8_t out[32]; + SHA256_Final(out, &ctx); + *hash = ToHexString(out, sizeof(out)); + } + return true; +} + std::optional GetHash(const std::string& path) { std::string content; if (!android::base::ReadFileToString(path, &content, true)) { diff --git a/init/Android.bp b/init/Android.bp index 3f2cd0714..c3dd7f6c6 100644 --- a/init/Android.bp +++ b/init/Android.bp @@ -129,6 +129,7 @@ cc_defaults { "libprotobuf-cpp-lite", "libpropertyinfoserializer", "libpropertyinfoparser", + "libsnapshot_cow", "libsnapshot_init", "libxml2", "lib_apex_manifest_proto_lite", diff --git a/init/Android.mk b/init/Android.mk index da94daf44..998e0fd46 100644 --- a/init/Android.mk +++ b/init/Android.mk @@ -112,6 +112,7 @@ LOCAL_STATIC_LIBRARIES := \ libmodprobe \ libext2_uuid \ libprotobuf-cpp-lite \ + libsnapshot_cow \ libsnapshot_init \ update_metadata-protos \