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 \