libsnapshot: Partially implement OpenSnapshotWriter.
This is a re-landing of the original CL, with a few changes: - The correct device is now returned in MapUpdateSnapshot. - The old API is used for tests, and the new API is only tested when used on a VABC device. - A sync() call has been added to ensure that writes to the base and target snapshot devices have been fully flushed. This makes IsPartitionUnchanged detect the MapUpdateSnapshot bug. 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 Test: full OTA with update_device.py Test: incremental OTA with update_device.py Change-Id: I90364a58902a4406a37cb14a816642c57a72bec2
This commit is contained in:
parent
f536731e5a
commit
aca0beaf77
16 changed files with 426 additions and 86 deletions
|
@ -147,6 +147,7 @@ cc_binary {
|
|||
static_libs: [
|
||||
"libgtest_prod",
|
||||
"libhealthhalutils",
|
||||
"libsnapshot_cow",
|
||||
"libsnapshot_nobinder",
|
||||
"update_metadata-protos",
|
||||
],
|
||||
|
|
|
@ -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",
|
||||
|
@ -247,6 +248,7 @@ cc_defaults {
|
|||
"libgmock",
|
||||
"liblp",
|
||||
"libsnapshot",
|
||||
"libsnapshot_cow",
|
||||
"libsnapshot_test_helpers",
|
||||
"libsparse",
|
||||
],
|
||||
|
@ -275,6 +277,7 @@ cc_binary {
|
|||
static_libs: [
|
||||
"libfstab",
|
||||
"libsnapshot",
|
||||
"libsnapshot_cow",
|
||||
"update_metadata-protos",
|
||||
],
|
||||
shared_libs: [
|
||||
|
@ -338,6 +341,7 @@ cc_defaults {
|
|||
"libgmock", // from libsnapshot_test_helpers
|
||||
"liblog",
|
||||
"liblp",
|
||||
"libsnapshot_cow",
|
||||
"libsnapshot_test_helpers",
|
||||
"libprotobuf-mutator",
|
||||
],
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -38,9 +38,7 @@ class MockSnapshotManager : public ISnapshotManager {
|
|||
(const android::fs_mgr::CreateLogicalPartitionParams& params,
|
||||
std::string* snapshot_path),
|
||||
(override));
|
||||
MOCK_METHOD(std::unique_ptr<ICowWriter>, OpenSnapshotWriter,
|
||||
(const android::fs_mgr::CreateLogicalPartitionParams& params), (override));
|
||||
MOCK_METHOD(std::unique_ptr<FileDescriptor>, OpenSnapshotReader,
|
||||
MOCK_METHOD(std::unique_ptr<ISnapshotWriter>, OpenSnapshotWriter,
|
||||
(const android::fs_mgr::CreateLogicalPartitionParams& params), (override));
|
||||
MOCK_METHOD(bool, UnmapUpdateSnapshot, (const std::string& target_partition_name), (override));
|
||||
MOCK_METHOD(bool, NeedSnapshotsInFirstStageMount, (), (override));
|
||||
|
|
|
@ -35,8 +35,8 @@
|
|||
#include <update_engine/update_metadata.pb.h>
|
||||
|
||||
#include <libsnapshot/auto_device.h>
|
||||
#include <libsnapshot/cow_writer.h>
|
||||
#include <libsnapshot/return.h>
|
||||
#include <libsnapshot/snapshot_writer.h>
|
||||
|
||||
#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<ICowWriter> 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<FileDescriptor> OpenSnapshotReader(
|
||||
// Create an ISnapshotWriter to build a snapshot against a target partition. The partition name
|
||||
// must be suffixed.
|
||||
virtual std::unique_ptr<ISnapshotWriter> 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<ICowWriter> OpenSnapshotWriter(
|
||||
const android::fs_mgr::CreateLogicalPartitionParams& params) override;
|
||||
std::unique_ptr<FileDescriptor> OpenSnapshotReader(
|
||||
std::unique_ptr<ISnapshotWriter> 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<ISnapshotWriter> OpenCompressedSnapshotWriter(LockedFile* lock,
|
||||
const std::string& partition_name,
|
||||
const SnapshotStatus& status,
|
||||
const SnapshotPaths& paths);
|
||||
std::unique_ptr<ISnapshotWriter> 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|:
|
||||
|
|
|
@ -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<ICowWriter> OpenSnapshotWriter(
|
||||
const android::fs_mgr::CreateLogicalPartitionParams& params) override;
|
||||
std::unique_ptr<FileDescriptor> OpenSnapshotReader(
|
||||
std::unique_ptr<ISnapshotWriter> OpenSnapshotWriter(
|
||||
const android::fs_mgr::CreateLogicalPartitionParams& params) override;
|
||||
bool UnmapUpdateSnapshot(const std::string& target_partition_name) override;
|
||||
bool NeedSnapshotsInFirstStageMount() override;
|
||||
|
|
68
fs_mgr/libsnapshot/include/libsnapshot/snapshot_writer.h
Normal file
68
fs_mgr/libsnapshot/include/libsnapshot/snapshot_writer.h
Normal file
|
@ -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 <android-base/unique_fd.h>
|
||||
|
||||
#include <libsnapshot/cow_writer.h>
|
||||
|
||||
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<FileDescriptor> 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<FileDescriptor> 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
|
|
@ -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<size_t> expect_size = std::nullopt,
|
||||
std::string* hash = nullptr);
|
||||
bool WriteRandomData(ICowWriter* writer, std::string* hash = nullptr);
|
||||
|
||||
std::optional<std::string> GetHash(const std::string& path);
|
||||
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include <libsnapshot/snapshot.h>
|
||||
|
||||
#include <dirent.h>
|
||||
#include <fcntl.h>
|
||||
#include <math.h>
|
||||
#include <sys/file.h>
|
||||
#include <sys/types.h>
|
||||
|
@ -1569,8 +1570,8 @@ bool SnapshotManager::CreateLogicalAndSnapshotPartitions(
|
|||
.partition_opener = &opener,
|
||||
.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 +1599,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 +1683,11 @@ bool SnapshotManager::MapPartitionWithSnapshot(LockedFile* lock,
|
|||
}
|
||||
created_devices.EmplaceBack<AutoUnmapDevice>(&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 +1714,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 +2453,85 @@ 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;
|
||||
}
|
||||
DCHECK(!snapshot_path->empty());
|
||||
return true;
|
||||
}
|
||||
|
||||
std::unique_ptr<ICowWriter> SnapshotManager::OpenSnapshotWriter(
|
||||
std::unique_ptr<ISnapshotWriter> 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);
|
||||
}
|
||||
return OpenKernelSnapshotWriter(lock.get(), params.GetPartitionName(), status, paths);
|
||||
}
|
||||
|
||||
std::unique_ptr<ISnapshotWriter> SnapshotManager::OpenCompressedSnapshotWriter(
|
||||
LockedFile*, const std::string&, const SnapshotStatus&, const SnapshotPaths&) {
|
||||
LOG(ERROR) << "OpenSnapshotWriter not yet implemented for compression";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<FileDescriptor> SnapshotManager::OpenSnapshotReader(
|
||||
const android::fs_mgr::CreateLogicalPartitionParams& params) {
|
||||
(void)params;
|
||||
std::unique_ptr<ISnapshotWriter> 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<OnlineKernelSnapshotWriter>(cow_options);
|
||||
|
||||
std::string path = paths.snapshot_device.empty() ? paths.target_device : paths.snapshot_device;
|
||||
unique_fd fd(open(path.c_str(), 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) {
|
||||
|
|
|
@ -130,13 +130,7 @@ ISnapshotMergeStats* SnapshotManagerStub::GetSnapshotMergeStatsInstance() {
|
|||
return &snapshot_merge_stats;
|
||||
}
|
||||
|
||||
std::unique_ptr<ICowWriter> SnapshotManagerStub::OpenSnapshotWriter(
|
||||
const CreateLogicalPartitionParams&) {
|
||||
LOG(ERROR) << __FUNCTION__ << " should never be called.";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::unique_ptr<FileDescriptor> SnapshotManagerStub::OpenSnapshotReader(
|
||||
std::unique_ptr<ISnapshotWriter> SnapshotManagerStub::OpenSnapshotWriter(
|
||||
const CreateLogicalPartitionParams&) {
|
||||
LOG(ERROR) << __FUNCTION__ << " should never be called.";
|
||||
return nullptr;
|
||||
|
|
|
@ -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,78 @@ 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<ICowWriter>* writer) {
|
||||
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_t> size = std::nullopt) {
|
||||
std::string path;
|
||||
auto res = MapUpdateSnapshot(name, &path);
|
||||
if (!res) {
|
||||
return res;
|
||||
AssertionResult MapUpdateSnapshot(const std::string& name, std::string* path) {
|
||||
CreateLogicalPartitionParams params{
|
||||
.block_device = fake_super,
|
||||
.metadata_slot = 1,
|
||||
.partition_name = name,
|
||||
.timeout_ms = 10s,
|
||||
.partition_opener = opener_.get(),
|
||||
};
|
||||
|
||||
auto result = sm->MapUpdateSnapshot(params, path);
|
||||
if (!result) {
|
||||
return AssertionFailure() << "Cannot open snapshot for writing: " << name;
|
||||
}
|
||||
return AssertionSuccess();
|
||||
}
|
||||
|
||||
AssertionResult MapUpdateSnapshot(const std::string& name) {
|
||||
if (IsCompressionEnabled()) {
|
||||
std::unique_ptr<ICowWriter> writer;
|
||||
return MapUpdateSnapshot(name, &writer);
|
||||
} else {
|
||||
std::string path;
|
||||
return MapUpdateSnapshot(name, &path);
|
||||
}
|
||||
}
|
||||
|
||||
AssertionResult WriteSnapshotAndHash(const std::string& name) {
|
||||
if (IsCompressionEnabled()) {
|
||||
std::unique_ptr<ICowWriter> writer;
|
||||
auto res = MapUpdateSnapshot(name, &writer);
|
||||
if (!res) {
|
||||
return res;
|
||||
}
|
||||
if (!WriteRandomData(writer.get(), &hashes_[name])) {
|
||||
return AssertionFailure() << "Unable to write random data to snapshot " << name;
|
||||
}
|
||||
} else {
|
||||
std::string path;
|
||||
auto res = MapUpdateSnapshot(name, &path);
|
||||
if (!res) {
|
||||
return res;
|
||||
}
|
||||
if (!WriteRandomData(path, std::nullopt, &hashes_[name])) {
|
||||
return AssertionFailure() << "Unable to write random data to snapshot " << name;
|
||||
}
|
||||
}
|
||||
|
||||
std::string size_string = size ? (std::to_string(*size) + " bytes") : "";
|
||||
// Make sure updates to one device are seen by all devices.
|
||||
sync();
|
||||
|
||||
if (!WriteRandomData(path, size, &hashes_[name])) {
|
||||
return AssertionFailure() << "Unable to write " << size_string << " to " << path
|
||||
<< " for partition " << 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 +1040,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 +1443,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 +1664,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 +1696,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<android::dm::DeviceMapper::TargetInfo> table;
|
||||
ASSERT_TRUE(DeviceMapper::Instance().GetTableStatus("sys_b", &table));
|
||||
|
|
91
fs_mgr/libsnapshot/snapshot_writer.cpp
Normal file
91
fs_mgr/libsnapshot/snapshot_writer.cpp
Normal file
|
@ -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 <libsnapshot/snapshot_writer.h>
|
||||
|
||||
#include <android-base/file.h>
|
||||
#include <android-base/logging.h>
|
||||
#include <payload_consumer/file_descriptor.h>
|
||||
|
||||
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<FileDescriptor> OnlineKernelSnapshotWriter::OpenReader() {
|
||||
LOG(ERROR) << "OnlineKernelSnapshotWriter::OpenReader not yet implemented";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace snapshot
|
||||
} // namespace android
|
|
@ -127,6 +127,48 @@ bool WriteRandomData(const std::string& path, std::optional<size_t> 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<std::string> GetHash(const std::string& path) {
|
||||
std::string content;
|
||||
if (!android::base::ReadFileToString(path, &content, true)) {
|
||||
|
|
|
@ -129,6 +129,7 @@ cc_defaults {
|
|||
"libprotobuf-cpp-lite",
|
||||
"libpropertyinfoserializer",
|
||||
"libpropertyinfoparser",
|
||||
"libsnapshot_cow",
|
||||
"libsnapshot_init",
|
||||
"libxml2",
|
||||
"lib_apex_manifest_proto_lite",
|
||||
|
|
|
@ -112,6 +112,7 @@ LOCAL_STATIC_LIBRARIES := \
|
|||
libmodprobe \
|
||||
libext2_uuid \
|
||||
libprotobuf-cpp-lite \
|
||||
libsnapshot_cow \
|
||||
libsnapshot_init \
|
||||
update_metadata-protos \
|
||||
|
||||
|
|
Loading…
Reference in a new issue