Merge changes from topic "libsnapshot_api_update_engine"
* changes: libsnapshot: tests for public APIs. fastbootd: skip COW group libsnapshot: no overlayfs during virtual a/b ota. libsnapshot: Also use empty space in super for COW libsnapshot: APIs for all partitions fs_mgr: CreateDmTable takes CreateLogicalPartitionParams
This commit is contained in:
commit
bd122aa9c2
|
@ -143,6 +143,10 @@ cc_binary {
|
|||
static_libs: [
|
||||
"libhealthhalutils",
|
||||
],
|
||||
|
||||
header_libs: [
|
||||
"libsnapshot_headers",
|
||||
]
|
||||
}
|
||||
|
||||
cc_defaults {
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
#include <fstab/fstab.h>
|
||||
#include <liblp/builder.h>
|
||||
#include <liblp/liblp.h>
|
||||
#include <libsnapshot/snapshot.h>
|
||||
#include <sparse/sparse.h>
|
||||
|
||||
#include "fastboot_device.h"
|
||||
|
@ -171,6 +172,11 @@ bool UpdateSuper(FastbootDevice* device, const std::string& super_name, bool wip
|
|||
if (!slot_suffix.empty() && GetPartitionSlotSuffix(partition_name) == slot_suffix) {
|
||||
continue;
|
||||
}
|
||||
std::string group_name = GetPartitionGroupName(old_metadata->groups[partition.group_index]);
|
||||
// Skip partitions in the COW group
|
||||
if (group_name == android::snapshot::kCowGroupName) {
|
||||
continue;
|
||||
}
|
||||
partitions_to_keep.emplace(partition_name);
|
||||
}
|
||||
|
||||
|
|
|
@ -79,22 +79,22 @@ static bool GetPhysicalPartitionDevicePath(const IPartitionOpener& opener,
|
|||
return true;
|
||||
}
|
||||
|
||||
bool CreateDmTable(const IPartitionOpener& opener, const LpMetadata& metadata,
|
||||
const LpMetadataPartition& partition, const std::string& super_device,
|
||||
DmTable* table) {
|
||||
bool CreateDmTableInternal(const CreateLogicalPartitionParams& params, DmTable* table) {
|
||||
const auto& super_device = params.block_device;
|
||||
|
||||
uint64_t sector = 0;
|
||||
for (size_t i = 0; i < partition.num_extents; i++) {
|
||||
const auto& extent = metadata.extents[partition.first_extent_index + i];
|
||||
for (size_t i = 0; i < params.partition->num_extents; i++) {
|
||||
const auto& extent = params.metadata->extents[params.partition->first_extent_index + i];
|
||||
std::unique_ptr<DmTarget> target;
|
||||
switch (extent.target_type) {
|
||||
case LP_TARGET_TYPE_ZERO:
|
||||
target = std::make_unique<DmTargetZero>(sector, extent.num_sectors);
|
||||
break;
|
||||
case LP_TARGET_TYPE_LINEAR: {
|
||||
const auto& block_device = metadata.block_devices[extent.target_source];
|
||||
const auto& block_device = params.metadata->block_devices[extent.target_source];
|
||||
std::string dev_string;
|
||||
if (!GetPhysicalPartitionDevicePath(opener, metadata, block_device, super_device,
|
||||
&dev_string)) {
|
||||
if (!GetPhysicalPartitionDevicePath(*params.partition_opener, *params.metadata,
|
||||
block_device, super_device, &dev_string)) {
|
||||
LOG(ERROR) << "Unable to complete device-mapper table, unknown block device";
|
||||
return false;
|
||||
}
|
||||
|
@ -111,12 +111,21 @@ bool CreateDmTable(const IPartitionOpener& opener, const LpMetadata& metadata,
|
|||
}
|
||||
sector += extent.num_sectors;
|
||||
}
|
||||
if (partition.attributes & LP_PARTITION_ATTR_READONLY) {
|
||||
if (params.partition->attributes & LP_PARTITION_ATTR_READONLY) {
|
||||
table->set_readonly(true);
|
||||
}
|
||||
if (params.force_writable) {
|
||||
table->set_readonly(false);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool CreateDmTable(CreateLogicalPartitionParams params, DmTable* table) {
|
||||
CreateLogicalPartitionParams::OwnedData owned_data;
|
||||
if (!params.InitDefaults(&owned_data)) return false;
|
||||
return CreateDmTableInternal(params, table);
|
||||
}
|
||||
|
||||
bool CreateLogicalPartitions(const std::string& block_device) {
|
||||
uint32_t slot = SlotNumberForSlotSuffix(fs_mgr_get_slot_suffix());
|
||||
auto metadata = ReadMetadata(block_device.c_str(), slot);
|
||||
|
@ -160,6 +169,11 @@ bool CreateLogicalPartitionParams::InitDefaults(CreateLogicalPartitionParams::Ow
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!partition_opener) {
|
||||
owned->partition_opener = std::make_unique<PartitionOpener>();
|
||||
partition_opener = owned->partition_opener.get();
|
||||
}
|
||||
|
||||
// Read metadata if needed.
|
||||
if (!metadata) {
|
||||
if (!metadata_slot) {
|
||||
|
@ -167,7 +181,8 @@ bool CreateLogicalPartitionParams::InitDefaults(CreateLogicalPartitionParams::Ow
|
|||
return false;
|
||||
}
|
||||
auto slot = *metadata_slot;
|
||||
if (owned->metadata = ReadMetadata(block_device, slot); !owned->metadata) {
|
||||
if (owned->metadata = ReadMetadata(*partition_opener, block_device, slot);
|
||||
!owned->metadata) {
|
||||
LOG(ERROR) << "Could not read partition table for: " << block_device;
|
||||
return false;
|
||||
}
|
||||
|
@ -195,11 +210,6 @@ bool CreateLogicalPartitionParams::InitDefaults(CreateLogicalPartitionParams::Ow
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!partition_opener) {
|
||||
owned->partition_opener = std::make_unique<PartitionOpener>();
|
||||
partition_opener = owned->partition_opener.get();
|
||||
}
|
||||
|
||||
if (device_name.empty()) {
|
||||
device_name = partition_name;
|
||||
}
|
||||
|
@ -212,13 +222,9 @@ bool CreateLogicalPartition(CreateLogicalPartitionParams params, std::string* pa
|
|||
if (!params.InitDefaults(&owned_data)) return false;
|
||||
|
||||
DmTable table;
|
||||
if (!CreateDmTable(*params.partition_opener, *params.metadata, *params.partition,
|
||||
params.block_device, &table)) {
|
||||
if (!CreateDmTableInternal(params, &table)) {
|
||||
return false;
|
||||
}
|
||||
if (params.force_writable) {
|
||||
table.set_readonly(false);
|
||||
}
|
||||
|
||||
DeviceMapper& dm = DeviceMapper::Instance();
|
||||
if (!dm.CreateDevice(params.device_name, table, path, params.timeout_ms)) {
|
||||
|
|
|
@ -105,9 +105,7 @@ bool CreateLogicalPartition(CreateLogicalPartitionParams params, std::string* pa
|
|||
bool DestroyLogicalPartition(const std::string& name);
|
||||
|
||||
// Helper for populating a DmTable for a logical partition.
|
||||
bool CreateDmTable(const IPartitionOpener& opener, const LpMetadata& metadata,
|
||||
const LpMetadataPartition& partition, const std::string& super_device,
|
||||
android::dm::DmTable* table);
|
||||
bool CreateDmTable(CreateLogicalPartitionParams params, android::dm::DmTable* table);
|
||||
|
||||
} // namespace fs_mgr
|
||||
} // namespace android
|
||||
|
|
|
@ -40,6 +40,10 @@ bool LinearExtent::AddTo(LpMetadata* out) const {
|
|||
return true;
|
||||
}
|
||||
|
||||
Interval LinearExtent::AsInterval() const {
|
||||
return Interval(device_index(), physical_sector(), end_sector());
|
||||
}
|
||||
|
||||
bool ZeroExtent::AddTo(LpMetadata* out) const {
|
||||
out->extents.emplace_back(LpMetadataExtent{num_sectors_, LP_TARGET_TYPE_ZERO, 0, 0});
|
||||
return true;
|
||||
|
@ -96,6 +100,20 @@ void Partition::ShrinkTo(uint64_t aligned_size) {
|
|||
DCHECK(size_ == aligned_size);
|
||||
}
|
||||
|
||||
Partition Partition::GetBeginningExtents(uint64_t aligned_size) const {
|
||||
Partition p(name_, group_name_, attributes_);
|
||||
for (const auto& extent : extents_) {
|
||||
auto le = extent->AsLinearExtent();
|
||||
if (le) {
|
||||
p.AddExtent(std::make_unique<LinearExtent>(*le));
|
||||
} else {
|
||||
p.AddExtent(std::make_unique<ZeroExtent>(extent->num_sectors()));
|
||||
}
|
||||
}
|
||||
p.ShrinkTo(aligned_size);
|
||||
return p;
|
||||
}
|
||||
|
||||
uint64_t Partition::BytesOnDisk() const {
|
||||
uint64_t sectors = 0;
|
||||
for (const auto& extent : extents_) {
|
||||
|
@ -602,6 +620,10 @@ std::vector<Interval> Interval::Intersect(const std::vector<Interval>& a,
|
|||
return ret;
|
||||
}
|
||||
|
||||
std::unique_ptr<Extent> Interval::AsExtent() const {
|
||||
return std::make_unique<LinearExtent>(length(), device_index, start);
|
||||
}
|
||||
|
||||
bool MetadataBuilder::GrowPartition(Partition* partition, uint64_t aligned_size,
|
||||
const std::vector<Interval>& free_region_hint) {
|
||||
uint64_t space_needed = aligned_size - partition->size();
|
||||
|
@ -1168,5 +1190,9 @@ std::string MetadataBuilder::GetBlockDevicePartitionName(uint64_t index) const {
|
|||
: "";
|
||||
}
|
||||
|
||||
uint64_t MetadataBuilder::logical_block_size() const {
|
||||
return geometry_.logical_block_size;
|
||||
}
|
||||
|
||||
} // namespace fs_mgr
|
||||
} // namespace android
|
||||
|
|
|
@ -33,10 +33,11 @@ namespace android {
|
|||
namespace fs_mgr {
|
||||
|
||||
class LinearExtent;
|
||||
struct Interval;
|
||||
|
||||
// By default, partitions are aligned on a 1MiB boundary.
|
||||
static const uint32_t kDefaultPartitionAlignment = 1024 * 1024;
|
||||
static const uint32_t kDefaultBlockSize = 4096;
|
||||
static constexpr uint32_t kDefaultPartitionAlignment = 1024 * 1024;
|
||||
static constexpr uint32_t kDefaultBlockSize = 4096;
|
||||
|
||||
// Name of the default group in a metadata.
|
||||
static constexpr std::string_view kDefaultGroup = "default";
|
||||
|
@ -74,6 +75,8 @@ class LinearExtent final : public Extent {
|
|||
return sector >= physical_sector_ && sector < end_sector();
|
||||
}
|
||||
|
||||
Interval AsInterval() const;
|
||||
|
||||
private:
|
||||
uint32_t device_index_;
|
||||
uint64_t physical_sector_;
|
||||
|
@ -127,6 +130,12 @@ class Partition final {
|
|||
const std::vector<std::unique_ptr<Extent>>& extents() const { return extents_; }
|
||||
uint64_t size() const { return size_; }
|
||||
|
||||
// Return a copy of *this, but with extents that includes only the first
|
||||
// |aligned_size| bytes. |aligned_size| should be aligned to
|
||||
// logical_block_size() of the MetadataBuilder that this partition belongs
|
||||
// to.
|
||||
Partition GetBeginningExtents(uint64_t aligned_size) const;
|
||||
|
||||
private:
|
||||
void ShrinkTo(uint64_t aligned_size);
|
||||
void set_group_name(std::string_view group_name) { group_name_ = group_name; }
|
||||
|
@ -156,6 +165,8 @@ struct Interval {
|
|||
return (start == other.start) ? end < other.end : start < other.start;
|
||||
}
|
||||
|
||||
std::unique_ptr<Extent> AsExtent() const;
|
||||
|
||||
// Intersect |a| with |b|.
|
||||
// If no intersection, result has 0 length().
|
||||
static Interval Intersect(const Interval& a, const Interval& b);
|
||||
|
@ -325,6 +336,8 @@ class MetadataBuilder {
|
|||
// Return the list of free regions not occupied by extents in the metadata.
|
||||
std::vector<Interval> GetFreeRegions() const;
|
||||
|
||||
uint64_t logical_block_size() const;
|
||||
|
||||
private:
|
||||
MetadataBuilder();
|
||||
MetadataBuilder(const MetadataBuilder&) = delete;
|
||||
|
|
|
@ -25,10 +25,12 @@ cc_defaults {
|
|||
shared_libs: [
|
||||
"libbase",
|
||||
"liblog",
|
||||
"liblp",
|
||||
],
|
||||
static_libs: [
|
||||
"libdm",
|
||||
"libfs_mgr",
|
||||
"libfstab",
|
||||
"liblp",
|
||||
],
|
||||
whole_static_libs: [
|
||||
|
@ -49,10 +51,17 @@ filegroup {
|
|||
name: "libsnapshot_sources",
|
||||
srcs: [
|
||||
"snapshot.cpp",
|
||||
"partition_cow_creator.cpp",
|
||||
"utility.cpp",
|
||||
],
|
||||
}
|
||||
|
||||
cc_library_headers {
|
||||
name: "libsnapshot_headers",
|
||||
recovery_available: true,
|
||||
defaults: ["libsnapshot_defaults"],
|
||||
}
|
||||
|
||||
cc_library_static {
|
||||
name: "libsnapshot",
|
||||
defaults: ["libsnapshot_defaults"],
|
||||
|
@ -77,6 +86,7 @@ cc_test {
|
|||
defaults: ["libsnapshot_defaults"],
|
||||
srcs: [
|
||||
"snapshot_test.cpp",
|
||||
"partition_cow_creator_test.cpp",
|
||||
"test_helpers.cpp",
|
||||
],
|
||||
shared_libs: [
|
||||
|
@ -90,5 +100,7 @@ cc_test {
|
|||
"libgmock",
|
||||
"liblp",
|
||||
"libsnapshot",
|
||||
"libsparse",
|
||||
"libz",
|
||||
],
|
||||
}
|
||||
|
|
|
@ -0,0 +1,77 @@
|
|||
// Copyright (C) 2019 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 <stdint.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
namespace android {
|
||||
namespace digital_storage {
|
||||
|
||||
template <size_t Power>
|
||||
struct Size {
|
||||
static constexpr size_t power = Power;
|
||||
constexpr Size(uint64_t count) : value_(count) {}
|
||||
|
||||
constexpr uint64_t bytes() const { return value_ << (Power * 10); }
|
||||
constexpr uint64_t count() const { return value_; }
|
||||
operator uint64_t() const { return bytes(); }
|
||||
|
||||
private:
|
||||
uint64_t value_;
|
||||
};
|
||||
|
||||
using B = Size<0>;
|
||||
using KiB = Size<1>;
|
||||
using MiB = Size<2>;
|
||||
using GiB = Size<3>;
|
||||
|
||||
constexpr B operator""_B(unsigned long long v) { // NOLINT
|
||||
return B{v};
|
||||
}
|
||||
|
||||
constexpr KiB operator""_KiB(unsigned long long v) { // NOLINT
|
||||
return KiB{v};
|
||||
}
|
||||
|
||||
constexpr MiB operator""_MiB(unsigned long long v) { // NOLINT
|
||||
return MiB{v};
|
||||
}
|
||||
|
||||
constexpr GiB operator""_GiB(unsigned long long v) { // NOLINT
|
||||
return GiB{v};
|
||||
}
|
||||
|
||||
template <typename Dest, typename Src>
|
||||
constexpr Dest size_cast(Src src) {
|
||||
if (Src::power < Dest::power) {
|
||||
return Dest(src.count() >> ((Dest::power - Src::power) * 10));
|
||||
}
|
||||
if (Src::power > Dest::power) {
|
||||
return Dest(src.count() << ((Src::power - Dest::power) * 10));
|
||||
}
|
||||
return Dest(src.count());
|
||||
}
|
||||
|
||||
static_assert((1_B).bytes() == 1);
|
||||
static_assert((1_KiB).bytes() == 1 << 10);
|
||||
static_assert((1_MiB).bytes() == 1 << 20);
|
||||
static_assert((1_GiB).bytes() == 1 << 30);
|
||||
static_assert(size_cast<KiB>(1_B).count() == 0);
|
||||
static_assert(size_cast<KiB>(1024_B).count() == 1);
|
||||
static_assert(size_cast<KiB>(1_MiB).count() == 1024);
|
||||
|
||||
} // namespace digital_storage
|
||||
} // namespace android
|
|
@ -17,13 +17,17 @@
|
|||
#include <stdint.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include <android-base/unique_fd.h>
|
||||
#include <fs_mgr_dm_linear.h>
|
||||
#include <libdm/dm.h>
|
||||
#include <libfiemap/image_manager.h>
|
||||
#include <liblp/builder.h>
|
||||
#include <liblp/liblp.h>
|
||||
|
||||
#ifndef FRIEND_TEST
|
||||
|
@ -45,6 +49,13 @@ class IPartitionOpener;
|
|||
|
||||
namespace snapshot {
|
||||
|
||||
struct AutoDeleteCowImage;
|
||||
struct AutoDeleteSnapshot;
|
||||
struct PartitionCowCreator;
|
||||
struct AutoDeviceList;
|
||||
|
||||
static constexpr const std::string_view kCowGroupName = "cow";
|
||||
|
||||
enum class UpdateState : unsigned int {
|
||||
// No update or merge is in progress.
|
||||
None,
|
||||
|
@ -75,8 +86,9 @@ enum class UpdateState : unsigned int {
|
|||
|
||||
class SnapshotManager final {
|
||||
using CreateLogicalPartitionParams = android::fs_mgr::CreateLogicalPartitionParams;
|
||||
using LpMetadata = android::fs_mgr::LpMetadata;
|
||||
using IPartitionOpener = android::fs_mgr::IPartitionOpener;
|
||||
using LpMetadata = android::fs_mgr::LpMetadata;
|
||||
using MetadataBuilder = android::fs_mgr::MetadataBuilder;
|
||||
|
||||
public:
|
||||
// Dependency injection for testing.
|
||||
|
@ -88,6 +100,7 @@ class SnapshotManager final {
|
|||
virtual std::string GetSlotSuffix() const = 0;
|
||||
virtual std::string GetSuperDevice(uint32_t slot) const = 0;
|
||||
virtual const IPartitionOpener& GetPartitionOpener() const = 0;
|
||||
virtual bool IsOverlayfsSetup() const = 0;
|
||||
};
|
||||
|
||||
~SnapshotManager();
|
||||
|
@ -153,6 +166,20 @@ class SnapshotManager final {
|
|||
// Other: 0
|
||||
UpdateState GetUpdateState(double* progress = nullptr);
|
||||
|
||||
// Create necessary COW device / files for OTA clients. New logical partitions will be added to
|
||||
// group "cow" in target_metadata. Regions of partitions of current_metadata will be
|
||||
// "write-protected" and snapshotted.
|
||||
bool CreateUpdateSnapshots(MetadataBuilder* target_metadata, const std::string& target_suffix,
|
||||
MetadataBuilder* current_metadata, const std::string& current_suffix,
|
||||
const std::map<std::string, uint64_t>& cow_sizes);
|
||||
|
||||
// Map a snapshotted partition for OTA clients to write to. Write-protected regions are
|
||||
// determined previously in CreateSnapshots.
|
||||
bool MapUpdateSnapshot(const CreateLogicalPartitionParams& params, std::string* snapshot_path);
|
||||
|
||||
// Unmap a snapshot device that's previously mapped with MapUpdateSnapshot.
|
||||
bool UnmapUpdateSnapshot(const std::string& target_partition_name);
|
||||
|
||||
// If this returns true, first-stage mount must call
|
||||
// CreateLogicalAndSnapshotPartitions rather than CreateLogicalPartitions.
|
||||
bool NeedSnapshotsInFirstStageMount();
|
||||
|
@ -173,7 +200,12 @@ class SnapshotManager final {
|
|||
FRIEND_TEST(SnapshotTest, Merge);
|
||||
FRIEND_TEST(SnapshotTest, MergeCannotRemoveCow);
|
||||
FRIEND_TEST(SnapshotTest, NoMergeBeforeReboot);
|
||||
FRIEND_TEST(SnapshotUpdateTest, SnapshotStatusFileWithoutCow);
|
||||
friend class SnapshotTest;
|
||||
friend class SnapshotUpdateTest;
|
||||
friend struct AutoDeleteCowImage;
|
||||
friend struct AutoDeleteSnapshot;
|
||||
friend struct PartitionCowCreator;
|
||||
|
||||
using DmTargetSnapshot = android::dm::DmTargetSnapshot;
|
||||
using IImageManager = android::fiemap::IImageManager;
|
||||
|
@ -241,9 +273,8 @@ class SnapshotManager final {
|
|||
// be mapped with two table entries: a dm-snapshot range covering
|
||||
// snapshot_size, and a dm-linear range covering the remainder.
|
||||
//
|
||||
// All sizes are specified in bytes, and the device, snapshot and COW partition sizes
|
||||
// must be a multiple of the sector size (512 bytes). COW file size will be rounded up
|
||||
// to the nearest sector.
|
||||
// All sizes are specified in bytes, and the device, snapshot, COW partition and COW file sizes
|
||||
// must be a multiple of the sector size (512 bytes).
|
||||
bool CreateSnapshot(LockedFile* lock, const std::string& name, SnapshotStatus status);
|
||||
|
||||
// |name| should be the base partition name (e.g. "system_a"). Create the
|
||||
|
@ -262,8 +293,7 @@ class SnapshotManager final {
|
|||
std::string* dev_path);
|
||||
|
||||
// Map a COW image that was previous created with CreateCowImage.
|
||||
bool MapCowImage(const std::string& name, const std::chrono::milliseconds& timeout_ms,
|
||||
std::string* cow_image_device);
|
||||
bool MapCowImage(const std::string& name, const std::chrono::milliseconds& timeout_ms);
|
||||
|
||||
// Remove the backing copy-on-write image and snapshot states for the named snapshot. The
|
||||
// caller is responsible for ensuring that the snapshot is unmapped.
|
||||
|
@ -344,6 +374,24 @@ class SnapshotManager final {
|
|||
bool MapPartitionWithSnapshot(LockedFile* lock, CreateLogicalPartitionParams params,
|
||||
std::string* path);
|
||||
|
||||
// Map the COW devices, including the partition in super and the images.
|
||||
// |params|:
|
||||
// - |partition_name| should be the name of the top-level partition (e.g. system_b),
|
||||
// not system_b-cow-img
|
||||
// - |device_name| and |partition| is ignored
|
||||
// - |timeout_ms| and the rest is respected
|
||||
// Return the path in |cow_device_path| (e.g. /dev/block/dm-1) and major:minor in
|
||||
// |cow_device_string|
|
||||
bool MapCowDevices(LockedFile* lock, const CreateLogicalPartitionParams& params,
|
||||
const SnapshotStatus& snapshot_status, AutoDeviceList* created_devices,
|
||||
std::string* cow_name);
|
||||
|
||||
// The reverse of MapCowDevices.
|
||||
bool UnmapCowDevices(LockedFile* lock, const std::string& name);
|
||||
|
||||
// The reverse of MapPartitionWithSnapshot.
|
||||
bool UnmapPartitionWithSnapshot(LockedFile* lock, const std::string& target_partition_name);
|
||||
|
||||
std::string gsid_dir_;
|
||||
std::string metadata_dir_;
|
||||
std::unique_ptr<IDeviceInfo> device_;
|
||||
|
|
|
@ -0,0 +1,176 @@
|
|||
// Copyright (C) 2019 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 "partition_cow_creator.h"
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#include <android-base/logging.h>
|
||||
|
||||
#include "utility.h"
|
||||
|
||||
using android::dm::kSectorSize;
|
||||
using android::fs_mgr::Extent;
|
||||
using android::fs_mgr::Interval;
|
||||
using android::fs_mgr::kDefaultBlockSize;
|
||||
using android::fs_mgr::Partition;
|
||||
|
||||
namespace android {
|
||||
namespace snapshot {
|
||||
|
||||
// Round |d| up to a multiple of |block_size|.
|
||||
static uint64_t RoundUp(double d, uint64_t block_size) {
|
||||
uint64_t ret = ((uint64_t)ceil(d) + block_size - 1) / block_size * block_size;
|
||||
CHECK(ret >= d) << "Can't round " << d << " up to a multiple of " << block_size;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Intersect two linear extents. If no intersection, return an extent with length 0.
|
||||
static std::unique_ptr<Extent> Intersect(Extent* target_extent, Extent* existing_extent) {
|
||||
// Convert target_extent and existing_extent to linear extents. Zero extents
|
||||
// doesn't matter and doesn't result in any intersection.
|
||||
auto existing_linear_extent = existing_extent->AsLinearExtent();
|
||||
if (!existing_extent) return nullptr;
|
||||
|
||||
auto target_linear_extent = target_extent->AsLinearExtent();
|
||||
if (!target_linear_extent) return nullptr;
|
||||
|
||||
return Interval::Intersect(target_linear_extent->AsInterval(),
|
||||
existing_linear_extent->AsInterval())
|
||||
.AsExtent();
|
||||
}
|
||||
|
||||
// Check that partition |p| contains |e| fully. Both of them should
|
||||
// be from |target_metadata|.
|
||||
// Returns true as long as |e| is a subrange of any extent of |p|.
|
||||
bool PartitionCowCreator::HasExtent(Partition* p, Extent* e) {
|
||||
for (auto& partition_extent : p->extents()) {
|
||||
auto intersection = Intersect(partition_extent.get(), e);
|
||||
if (intersection != nullptr && intersection->num_sectors() == e->num_sectors()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Return the number of sectors, N, where |target_partition|[0..N] (from
|
||||
// |target_metadata|) are the sectors that should be snapshotted. N is computed
|
||||
// so that this range of sectors are used by partitions in |current_metadata|.
|
||||
//
|
||||
// The client code (update_engine) should have computed target_metadata by
|
||||
// resizing partitions of current_metadata, so only the first N sectors should
|
||||
// be snapshotted, not a range with start index != 0.
|
||||
//
|
||||
// Note that if partition A has shrunk and partition B has grown, the new
|
||||
// extents of partition B may use the empty space that was used by partition A.
|
||||
// In this case, that new extent cannot be written directly, as it may be used
|
||||
// by the running system. Hence, all extents of the new partition B must be
|
||||
// intersected with all old partitions (including old partition A and B) to get
|
||||
// the region that needs to be snapshotted.
|
||||
std::optional<uint64_t> PartitionCowCreator::GetSnapshotSize() {
|
||||
// Compute the number of sectors that needs to be snapshotted.
|
||||
uint64_t snapshot_sectors = 0;
|
||||
std::vector<std::unique_ptr<Extent>> intersections;
|
||||
for (const auto& extent : target_partition->extents()) {
|
||||
for (auto* existing_partition :
|
||||
ListPartitionsWithSuffix(current_metadata, current_suffix)) {
|
||||
for (const auto& existing_extent : existing_partition->extents()) {
|
||||
auto intersection = Intersect(extent.get(), existing_extent.get());
|
||||
if (intersection != nullptr && intersection->num_sectors() > 0) {
|
||||
snapshot_sectors += intersection->num_sectors();
|
||||
intersections.emplace_back(std::move(intersection));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
uint64_t snapshot_size = snapshot_sectors * kSectorSize;
|
||||
|
||||
// Sanity check that all recorded intersections are indeed within
|
||||
// target_partition[0..snapshot_sectors].
|
||||
Partition target_partition_snapshot = target_partition->GetBeginningExtents(snapshot_size);
|
||||
for (const auto& intersection : intersections) {
|
||||
if (!HasExtent(&target_partition_snapshot, intersection.get())) {
|
||||
auto linear_intersection = intersection->AsLinearExtent();
|
||||
LOG(ERROR) << "Extent "
|
||||
<< (linear_intersection
|
||||
? (std::to_string(linear_intersection->physical_sector()) + "," +
|
||||
std::to_string(linear_intersection->end_sector()))
|
||||
: "")
|
||||
<< " is not part of Partition " << target_partition->name() << "[0.."
|
||||
<< snapshot_size
|
||||
<< "]. The metadata wasn't constructed correctly. This should not happen.";
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
return snapshot_size;
|
||||
}
|
||||
|
||||
std::optional<PartitionCowCreator::Return> PartitionCowCreator::Run() {
|
||||
static constexpr double kCowEstimateFactor = 1.05;
|
||||
|
||||
CHECK(current_metadata->GetBlockDevicePartitionName(0) == LP_METADATA_DEFAULT_PARTITION_NAME &&
|
||||
target_metadata->GetBlockDevicePartitionName(0) == LP_METADATA_DEFAULT_PARTITION_NAME);
|
||||
|
||||
uint64_t logical_block_size = current_metadata->logical_block_size();
|
||||
CHECK(logical_block_size != 0 && !(logical_block_size & (logical_block_size - 1)))
|
||||
<< "logical_block_size is not power of 2";
|
||||
|
||||
Return ret;
|
||||
ret.snapshot_status.device_size = target_partition->size();
|
||||
|
||||
auto snapshot_size = GetSnapshotSize();
|
||||
if (!snapshot_size.has_value()) return std::nullopt;
|
||||
|
||||
ret.snapshot_status.snapshot_size = *snapshot_size;
|
||||
|
||||
// TODO: always read from cow_size when the COW size is written in
|
||||
// update package. kCowEstimateFactor is good for prototyping but
|
||||
// we can't use that in production.
|
||||
if (!cow_size.has_value()) {
|
||||
cow_size =
|
||||
RoundUp(ret.snapshot_status.snapshot_size * kCowEstimateFactor, kDefaultBlockSize);
|
||||
}
|
||||
|
||||
// Compute regions that are free in both current and target metadata. These are the regions
|
||||
// we can use for COW partition.
|
||||
auto target_free_regions = target_metadata->GetFreeRegions();
|
||||
auto current_free_regions = current_metadata->GetFreeRegions();
|
||||
auto free_regions = Interval::Intersect(target_free_regions, current_free_regions);
|
||||
uint64_t free_region_length = 0;
|
||||
for (const auto& interval : free_regions) {
|
||||
free_region_length += interval.length() * kSectorSize;
|
||||
}
|
||||
|
||||
LOG(INFO) << "Remaining free space for COW: " << free_region_length << " bytes";
|
||||
|
||||
// Compute the COW partition size.
|
||||
ret.snapshot_status.cow_partition_size = std::min(*cow_size, free_region_length);
|
||||
// Round it down to the nearest logical block. Logical partitions must be a multiple
|
||||
// of logical blocks.
|
||||
ret.snapshot_status.cow_partition_size &= ~(logical_block_size - 1);
|
||||
// Assign cow_partition_usable_regions to indicate what regions should the COW partition uses.
|
||||
ret.cow_partition_usable_regions = std::move(free_regions);
|
||||
|
||||
// The rest of the COW space is allocated on ImageManager.
|
||||
ret.snapshot_status.cow_file_size = (*cow_size) - ret.snapshot_status.cow_partition_size;
|
||||
// Round it up to the nearest sector.
|
||||
ret.snapshot_status.cow_file_size += kSectorSize - 1;
|
||||
ret.snapshot_status.cow_file_size &= ~(kSectorSize - 1);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // namespace snapshot
|
||||
} // namespace android
|
|
@ -0,0 +1,63 @@
|
|||
// Copyright (C) 2019 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 <stdint.h>
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include <liblp/builder.h>
|
||||
|
||||
#include <libsnapshot/snapshot.h>
|
||||
|
||||
namespace android {
|
||||
namespace snapshot {
|
||||
|
||||
// Helper class that creates COW for a partition.
|
||||
struct PartitionCowCreator {
|
||||
using Extent = android::fs_mgr::Extent;
|
||||
using Interval = android::fs_mgr::Interval;
|
||||
using MetadataBuilder = android::fs_mgr::MetadataBuilder;
|
||||
using Partition = android::fs_mgr::Partition;
|
||||
|
||||
// The metadata that will be written to target metadata slot.
|
||||
MetadataBuilder* target_metadata;
|
||||
// The suffix of the target slot.
|
||||
std::string target_suffix;
|
||||
// The partition in target_metadata that needs to be snapshotted.
|
||||
Partition* target_partition;
|
||||
// The metadata at the current slot (that would be used if the device boots
|
||||
// normally). This is used to determine which extents are being used.
|
||||
MetadataBuilder* current_metadata;
|
||||
// The suffix of the current slot.
|
||||
std::string current_suffix;
|
||||
// The COW size given by client code.
|
||||
std::optional<uint64_t> cow_size;
|
||||
|
||||
struct Return {
|
||||
SnapshotManager::SnapshotStatus snapshot_status;
|
||||
std::vector<Interval> cow_partition_usable_regions;
|
||||
};
|
||||
|
||||
std::optional<Return> Run();
|
||||
|
||||
private:
|
||||
bool HasExtent(Partition* p, Extent* e);
|
||||
std::optional<uint64_t> GetSnapshotSize();
|
||||
};
|
||||
|
||||
} // namespace snapshot
|
||||
} // namespace android
|
|
@ -0,0 +1,90 @@
|
|||
// Copyright (C) 2018 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 <gmock/gmock.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <liblp/builder.h>
|
||||
#include <liblp/property_fetcher.h>
|
||||
|
||||
#include "partition_cow_creator.h"
|
||||
|
||||
using ::android::fs_mgr::MetadataBuilder;
|
||||
using ::testing::_;
|
||||
using ::testing::AnyNumber;
|
||||
using ::testing::Return;
|
||||
|
||||
namespace android {
|
||||
namespace snapshot {
|
||||
|
||||
class MockPropertyFetcher : public fs_mgr::IPropertyFetcher {
|
||||
public:
|
||||
MOCK_METHOD2(GetProperty, std::string(const std::string&, const std::string&));
|
||||
MOCK_METHOD2(GetBoolProperty, bool(const std::string&, bool));
|
||||
};
|
||||
|
||||
class PartitionCowCreatorTest : ::testing::Test {
|
||||
public:
|
||||
void SetUp() override {
|
||||
fs_mgr::IPropertyFetcher::OverrideForTesting(std::make_unique<MockPropertyFetcher>());
|
||||
|
||||
EXPECT_CALL(fetcher(), GetProperty("ro.boot.slot_suffix", _))
|
||||
.Times(AnyNumber())
|
||||
.WillRepeatedly(Return("_a"));
|
||||
EXPECT_CALL(fetcher(), GetBoolProperty("ro.boot.dynamic_partitions", _))
|
||||
.Times(AnyNumber())
|
||||
.WillRepeatedly(Return(true));
|
||||
EXPECT_CALL(fetcher(), GetBoolProperty("ro.boot.dynamic_partitions_retrofit", _))
|
||||
.Times(AnyNumber())
|
||||
.WillRepeatedly(Return(false));
|
||||
EXPECT_CALL(fetcher(), GetBoolProperty("ro.virtual_ab.enabled", _))
|
||||
.Times(AnyNumber())
|
||||
.WillRepeatedly(Return(true));
|
||||
}
|
||||
void TearDown() override {
|
||||
fs_mgr::IPropertyFetcher::OverrideForTesting(std::make_unique<MockPropertyFetcher>());
|
||||
}
|
||||
MockPropertyFetcher& fetcher() {
|
||||
return *static_cast<MockPropertyFetcher*>(fs_mgr::IPropertyFetcher::GetInstance());
|
||||
}
|
||||
};
|
||||
|
||||
TEST(PartitionCowCreator, IntersectSelf) {
|
||||
auto builder_a = MetadataBuilder::New(1024 * 1024, 1024, 2);
|
||||
ASSERT_NE(builder_a, nullptr);
|
||||
auto system_a = builder_a->AddPartition("system_a", LP_PARTITION_ATTR_READONLY);
|
||||
ASSERT_NE(system_a, nullptr);
|
||||
ASSERT_TRUE(builder_a->ResizePartition(system_a, 40 * 1024));
|
||||
|
||||
auto builder_b = MetadataBuilder::New(1024 * 1024, 1024, 2);
|
||||
ASSERT_NE(builder_b, nullptr);
|
||||
auto system_b = builder_b->AddPartition("system_b", LP_PARTITION_ATTR_READONLY);
|
||||
ASSERT_NE(system_b, nullptr);
|
||||
ASSERT_TRUE(builder_b->ResizePartition(system_b, 40 * 1024));
|
||||
|
||||
PartitionCowCreator creator{.target_metadata = builder_b.get(),
|
||||
.target_suffix = "_b",
|
||||
.target_partition = system_b,
|
||||
.current_metadata = builder_a.get(),
|
||||
.current_suffix = "_a",
|
||||
.cow_size = 20 * 1024};
|
||||
auto ret = creator.Run();
|
||||
ASSERT_TRUE(ret.has_value());
|
||||
ASSERT_EQ(40 * 1024, ret->snapshot_status.device_size);
|
||||
ASSERT_EQ(40 * 1024, ret->snapshot_status.snapshot_size);
|
||||
ASSERT_EQ(20 * 1024,
|
||||
ret->snapshot_status.cow_file_size + ret->snapshot_status.cow_partition_size);
|
||||
}
|
||||
|
||||
} // namespace snapshot
|
||||
} // namespace android
|
|
@ -15,6 +15,7 @@
|
|||
#include <libsnapshot/snapshot.h>
|
||||
|
||||
#include <dirent.h>
|
||||
#include <math.h>
|
||||
#include <sys/file.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/unistd.h>
|
||||
|
@ -31,11 +32,13 @@
|
|||
#include <ext4_utils/ext4_utils.h>
|
||||
#include <fs_mgr.h>
|
||||
#include <fs_mgr_dm_linear.h>
|
||||
#include <fs_mgr_overlayfs.h>
|
||||
#include <fstab/fstab.h>
|
||||
#include <libdm/dm.h>
|
||||
#include <libfiemap/image_manager.h>
|
||||
#include <liblp/liblp.h>
|
||||
|
||||
#include "partition_cow_creator.h"
|
||||
#include "utility.h"
|
||||
|
||||
namespace android {
|
||||
|
@ -53,8 +56,10 @@ using android::fiemap::IImageManager;
|
|||
using android::fs_mgr::CreateDmTable;
|
||||
using android::fs_mgr::CreateLogicalPartition;
|
||||
using android::fs_mgr::CreateLogicalPartitionParams;
|
||||
using android::fs_mgr::GetPartitionGroupName;
|
||||
using android::fs_mgr::GetPartitionName;
|
||||
using android::fs_mgr::LpMetadata;
|
||||
using android::fs_mgr::MetadataBuilder;
|
||||
using android::fs_mgr::SlotNumberForSlotSuffix;
|
||||
using std::chrono::duration_cast;
|
||||
using namespace std::chrono_literals;
|
||||
|
@ -73,6 +78,7 @@ class DeviceInfo final : public SnapshotManager::IDeviceInfo {
|
|||
std::string GetSuperDevice(uint32_t slot) const override {
|
||||
return fs_mgr_get_super_partition_name(slot);
|
||||
}
|
||||
bool IsOverlayfsSetup() const override { return fs_mgr_overlayfs_is_setup(); }
|
||||
|
||||
private:
|
||||
android::fs_mgr::PartitionOpener opener_;
|
||||
|
@ -102,7 +108,7 @@ SnapshotManager::SnapshotManager(IDeviceInfo* device) : device_(device) {
|
|||
metadata_dir_ = device_->GetMetadataDir();
|
||||
}
|
||||
|
||||
[[maybe_unused]] static std::string GetCowName(const std::string& snapshot_name) {
|
||||
static std::string GetCowName(const std::string& snapshot_name) {
|
||||
return snapshot_name + "-cow";
|
||||
}
|
||||
|
||||
|
@ -205,7 +211,7 @@ bool SnapshotManager::CreateSnapshot(LockedFile* lock, const std::string& name,
|
|||
CHECK(lock->lock_mode() == LOCK_EX);
|
||||
// Sanity check these sizes. Like liblp, we guarantee the partition size
|
||||
// is respected, which means it has to be sector-aligned. (This guarantee
|
||||
// is useful for locating avb footers correctly). The COW size, however,
|
||||
// is useful for locating avb footers correctly). The COW file size, however,
|
||||
// can be arbitrarily larger than specified, so we can safely round it up.
|
||||
if (status.device_size % kSectorSize != 0) {
|
||||
LOG(ERROR) << "Snapshot " << name
|
||||
|
@ -217,10 +223,17 @@ bool SnapshotManager::CreateSnapshot(LockedFile* lock, const std::string& name,
|
|||
<< status.snapshot_size;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Round the COW size up to the nearest sector.
|
||||
status.cow_file_size += kSectorSize - 1;
|
||||
status.cow_file_size &= ~(kSectorSize - 1);
|
||||
if (status.cow_partition_size % kSectorSize != 0) {
|
||||
LOG(ERROR) << "Snapshot " << name
|
||||
<< " cow partition size is not a multiple of the sector size: "
|
||||
<< status.cow_partition_size;
|
||||
return false;
|
||||
}
|
||||
if (status.cow_file_size % kSectorSize != 0) {
|
||||
LOG(ERROR) << "Snapshot " << name << " cow file size is not a multiple of the sector size: "
|
||||
<< status.cow_partition_size;
|
||||
return false;
|
||||
}
|
||||
|
||||
status.state = SnapshotState::Created;
|
||||
status.sectors_allocated = 0;
|
||||
|
@ -253,26 +266,7 @@ bool SnapshotManager::CreateCowImage(LockedFile* lock, const std::string& name)
|
|||
|
||||
std::string cow_image_name = GetCowImageDeviceName(name);
|
||||
int cow_flags = IImageManager::CREATE_IMAGE_DEFAULT;
|
||||
if (!images_->CreateBackingImage(cow_image_name, status.cow_file_size, cow_flags)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// when the kernel creates a persistent dm-snapshot, it requires a CoW file
|
||||
// to store the modifications. The kernel interface does not specify how
|
||||
// the CoW is used, and there is no standard associated.
|
||||
// By looking at the current implementation, the CoW file is treated as:
|
||||
// - a _NEW_ snapshot if its first 32 bits are zero, so the newly created
|
||||
// dm-snapshot device will look like a perfect copy of the origin device;
|
||||
// - an _EXISTING_ snapshot if the first 32 bits are equal to a
|
||||
// kernel-specified magic number and the CoW file metadata is set as valid,
|
||||
// so it can be used to resume the last state of a snapshot device;
|
||||
// - an _INVALID_ snapshot otherwise.
|
||||
// To avoid zero-filling the whole CoW file when a new dm-snapshot is
|
||||
// created, here we zero-fill only the first 32 bits. This is a temporary
|
||||
// workaround that will be discussed again when the kernel API gets
|
||||
// consolidated.
|
||||
ssize_t dm_snap_magic_size = 4; // 32 bit
|
||||
return images_->ZeroFillNewImage(cow_image_name, dm_snap_magic_size);
|
||||
return images_->CreateBackingImage(cow_image_name, status.cow_file_size, cow_flags);
|
||||
}
|
||||
|
||||
bool SnapshotManager::MapSnapshot(LockedFile* lock, const std::string& name,
|
||||
|
@ -390,21 +384,24 @@ bool SnapshotManager::MapSnapshot(LockedFile* lock, const std::string& name,
|
|||
}
|
||||
|
||||
bool SnapshotManager::MapCowImage(const std::string& name,
|
||||
const std::chrono::milliseconds& timeout_ms,
|
||||
std::string* cow_dev) {
|
||||
const std::chrono::milliseconds& timeout_ms) {
|
||||
if (!EnsureImageManager()) return false;
|
||||
auto cow_image_name = GetCowImageDeviceName(name);
|
||||
|
||||
bool ok;
|
||||
std::string cow_dev;
|
||||
if (has_local_image_manager_) {
|
||||
// If we forced a local image manager, it means we don't have binder,
|
||||
// which means first-stage init. We must use device-mapper.
|
||||
const auto& opener = device_->GetPartitionOpener();
|
||||
ok = images_->MapImageWithDeviceMapper(opener, cow_image_name, cow_dev);
|
||||
ok = images_->MapImageWithDeviceMapper(opener, cow_image_name, &cow_dev);
|
||||
} else {
|
||||
ok = images_->MapImageDevice(cow_image_name, timeout_ms, cow_dev);
|
||||
ok = images_->MapImageDevice(cow_image_name, timeout_ms, &cow_dev);
|
||||
}
|
||||
if (!ok) {
|
||||
|
||||
if (ok) {
|
||||
LOG(INFO) << "Mapped " << cow_image_name << " to " << cow_dev;
|
||||
} else {
|
||||
LOG(ERROR) << "Could not map image device: " << cow_image_name;
|
||||
}
|
||||
return ok;
|
||||
|
@ -413,22 +410,15 @@ bool SnapshotManager::MapCowImage(const std::string& name,
|
|||
bool SnapshotManager::UnmapSnapshot(LockedFile* lock, const std::string& name) {
|
||||
CHECK(lock);
|
||||
|
||||
SnapshotStatus status;
|
||||
if (!ReadSnapshotStatus(lock, name, &status)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto& dm = DeviceMapper::Instance();
|
||||
if (!dm.DeleteDeviceIfExists(name)) {
|
||||
LOG(ERROR) << "Could not delete snapshot device: " << name;
|
||||
return false;
|
||||
}
|
||||
|
||||
// There may be an extra device, since the kernel doesn't let us have a
|
||||
// snapshot and linear target in the same table.
|
||||
auto dm_name = GetSnapshotDeviceName(name, status);
|
||||
if (name != dm_name && !dm.DeleteDeviceIfExists(dm_name)) {
|
||||
LOG(ERROR) << "Could not delete inner snapshot device: " << dm_name;
|
||||
auto snapshot_extra_device = GetSnapshotExtraDeviceName(name);
|
||||
if (!dm.DeleteDeviceIfExists(snapshot_extra_device)) {
|
||||
LOG(ERROR) << "Could not delete snapshot inner device: " << snapshot_extra_device;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -445,11 +435,12 @@ bool SnapshotManager::DeleteSnapshot(LockedFile* lock, const std::string& name)
|
|||
CHECK(lock->lock_mode() == LOCK_EX);
|
||||
if (!EnsureImageManager()) return false;
|
||||
|
||||
if (!UnmapCowDevices(lock, name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto cow_image_name = GetCowImageDeviceName(name);
|
||||
if (images_->BackingImageExists(cow_image_name)) {
|
||||
if (!images_->UnmapImageIfExists(cow_image_name)) {
|
||||
return false;
|
||||
}
|
||||
if (!images_->DeleteBackingImage(cow_image_name)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -992,24 +983,16 @@ bool SnapshotManager::CollapseSnapshotDevice(const std::string& name,
|
|||
}
|
||||
}
|
||||
|
||||
// Grab the partition metadata for the snapshot.
|
||||
uint32_t slot = SlotNumberForSlotSuffix(device_->GetSlotSuffix());
|
||||
auto super_device = device_->GetSuperDevice(slot);
|
||||
const auto& opener = device_->GetPartitionOpener();
|
||||
auto metadata = android::fs_mgr::ReadMetadata(opener, super_device, slot);
|
||||
if (!metadata) {
|
||||
LOG(ERROR) << "Could not read super partition metadata.";
|
||||
return false;
|
||||
}
|
||||
auto partition = android::fs_mgr::FindPartition(*metadata.get(), name);
|
||||
if (!partition) {
|
||||
LOG(ERROR) << "Snapshot does not have a partition in super: " << name;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Create a DmTable that is identical to the base device.
|
||||
CreateLogicalPartitionParams base_device_params{
|
||||
.block_device = device_->GetSuperDevice(slot),
|
||||
.metadata_slot = slot,
|
||||
.partition_name = name,
|
||||
.partition_opener = &device_->GetPartitionOpener(),
|
||||
};
|
||||
DmTable table;
|
||||
if (!CreateDmTable(opener, *metadata.get(), *partition, super_device, &table)) {
|
||||
if (!CreateDmTable(base_device_params, &table)) {
|
||||
LOG(ERROR) << "Could not create a DmTable for partition: " << name;
|
||||
return false;
|
||||
}
|
||||
|
@ -1086,7 +1069,7 @@ bool SnapshotManager::RemoveAllSnapshots(LockedFile* lock) {
|
|||
|
||||
bool ok = true;
|
||||
for (const auto& name : snapshots) {
|
||||
ok &= DeleteSnapshot(lock, name);
|
||||
ok &= (UnmapPartitionWithSnapshot(lock, name) && DeleteSnapshot(lock, name));
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
@ -1188,6 +1171,12 @@ bool SnapshotManager::CreateLogicalAndSnapshotPartitions(const std::string& supe
|
|||
}
|
||||
|
||||
for (const auto& partition : metadata->partitions) {
|
||||
if (GetPartitionGroupName(metadata->groups[partition.group_index]) == kCowGroupName) {
|
||||
LOG(INFO) << "Skip mapping partition " << GetPartitionName(partition) << " in group "
|
||||
<< kCowGroupName;
|
||||
continue;
|
||||
}
|
||||
|
||||
CreateLogicalPartitionParams params = {
|
||||
.block_device = super_device,
|
||||
.metadata = metadata.get(),
|
||||
|
@ -1229,6 +1218,12 @@ bool SnapshotManager::MapPartitionWithSnapshot(LockedFile* lock,
|
|||
CHECK(lock);
|
||||
path->clear();
|
||||
|
||||
if (params.GetPartitionName() != params.GetDeviceName()) {
|
||||
LOG(ERROR) << "Mapping snapshot with a different name is unsupported: partition_name = "
|
||||
<< params.GetPartitionName() << ", device_name = " << params.GetDeviceName();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Fill out fields in CreateLogicalPartitionParams so that we have more information (e.g. by
|
||||
// reading super partition metadata).
|
||||
CreateLogicalPartitionParams::OwnedData params_owned_data;
|
||||
|
@ -1304,22 +1299,20 @@ bool SnapshotManager::MapPartitionWithSnapshot(LockedFile* lock,
|
|||
return false;
|
||||
}
|
||||
|
||||
// If there is a timeout specified, compute the remaining time to call Map* functions.
|
||||
// init calls CreateLogicalAndSnapshotPartitions, which has no timeout specified. Still call
|
||||
// Map* functions in this case.
|
||||
auto remaining_time = GetRemainingTime(params.timeout_ms, begin);
|
||||
if (remaining_time.count() < 0) return false;
|
||||
|
||||
std::string cow_image_device;
|
||||
if (!MapCowImage(params.GetPartitionName(), remaining_time, &cow_image_device)) {
|
||||
LOG(ERROR) << "Could not map cow image for partition: " << params.GetPartitionName();
|
||||
std::string cow_name;
|
||||
CreateLogicalPartitionParams cow_params = params;
|
||||
cow_params.timeout_ms = remaining_time;
|
||||
if (!MapCowDevices(lock, cow_params, *live_snapshot_status, &created_devices, &cow_name)) {
|
||||
return false;
|
||||
}
|
||||
std::string cow_device;
|
||||
if (!dm.GetDeviceString(cow_name, &cow_device)) {
|
||||
LOG(ERROR) << "Could not determine major/minor for: " << cow_name;
|
||||
return false;
|
||||
}
|
||||
created_devices.EmplaceBack<AutoUnmapImage>(images_.get(),
|
||||
GetCowImageDeviceName(params.partition_name));
|
||||
|
||||
// TODO: map cow linear device here
|
||||
std::string cow_device = cow_image_device;
|
||||
|
||||
remaining_time = GetRemainingTime(params.timeout_ms, begin);
|
||||
if (remaining_time.count() < 0) return false;
|
||||
|
@ -1338,6 +1331,121 @@ bool SnapshotManager::MapPartitionWithSnapshot(LockedFile* lock,
|
|||
return true;
|
||||
}
|
||||
|
||||
bool SnapshotManager::UnmapPartitionWithSnapshot(LockedFile* lock,
|
||||
const std::string& target_partition_name) {
|
||||
CHECK(lock);
|
||||
|
||||
if (!UnmapSnapshot(lock, target_partition_name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!UnmapCowDevices(lock, target_partition_name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto& dm = DeviceMapper::Instance();
|
||||
std::string base_name = GetBaseDeviceName(target_partition_name);
|
||||
if (!dm.DeleteDeviceIfExists(base_name)) {
|
||||
LOG(ERROR) << "Cannot delete base device: " << base_name;
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG(INFO) << "Successfully unmapped snapshot " << target_partition_name;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SnapshotManager::MapCowDevices(LockedFile* lock, const CreateLogicalPartitionParams& params,
|
||||
const SnapshotStatus& snapshot_status,
|
||||
AutoDeviceList* created_devices, std::string* cow_name) {
|
||||
CHECK(lock);
|
||||
if (!EnsureImageManager()) return false;
|
||||
CHECK(snapshot_status.cow_partition_size + snapshot_status.cow_file_size > 0);
|
||||
auto begin = std::chrono::steady_clock::now();
|
||||
|
||||
std::string partition_name = params.GetPartitionName();
|
||||
std::string cow_image_name = GetCowImageDeviceName(partition_name);
|
||||
*cow_name = GetCowName(partition_name);
|
||||
|
||||
auto& dm = DeviceMapper::Instance();
|
||||
|
||||
// Map COW image if necessary.
|
||||
if (snapshot_status.cow_file_size > 0) {
|
||||
auto remaining_time = GetRemainingTime(params.timeout_ms, begin);
|
||||
if (remaining_time.count() < 0) return false;
|
||||
|
||||
if (!MapCowImage(partition_name, remaining_time)) {
|
||||
LOG(ERROR) << "Could not map cow image for partition: " << partition_name;
|
||||
return false;
|
||||
}
|
||||
created_devices->EmplaceBack<AutoUnmapImage>(images_.get(), cow_image_name);
|
||||
|
||||
// If no COW partition exists, just return the image alone.
|
||||
if (snapshot_status.cow_partition_size == 0) {
|
||||
*cow_name = std::move(cow_image_name);
|
||||
LOG(INFO) << "Mapped COW image for " << partition_name << " at " << *cow_name;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
auto remaining_time = GetRemainingTime(params.timeout_ms, begin);
|
||||
if (remaining_time.count() < 0) return false;
|
||||
|
||||
CHECK(snapshot_status.cow_partition_size > 0);
|
||||
|
||||
// Create the DmTable for the COW device. It is the DmTable of the COW partition plus
|
||||
// COW image device as the last extent.
|
||||
CreateLogicalPartitionParams cow_partition_params = params;
|
||||
cow_partition_params.partition = nullptr;
|
||||
cow_partition_params.partition_name = *cow_name;
|
||||
cow_partition_params.device_name.clear();
|
||||
DmTable table;
|
||||
if (!CreateDmTable(cow_partition_params, &table)) {
|
||||
return false;
|
||||
}
|
||||
// If the COW image exists, append it as the last extent.
|
||||
if (snapshot_status.cow_file_size > 0) {
|
||||
std::string cow_image_device;
|
||||
if (!dm.GetDeviceString(cow_image_name, &cow_image_device)) {
|
||||
LOG(ERROR) << "Cannot determine major/minor for: " << cow_image_name;
|
||||
return false;
|
||||
}
|
||||
auto cow_partition_sectors = snapshot_status.cow_partition_size / kSectorSize;
|
||||
auto cow_image_sectors = snapshot_status.cow_file_size / kSectorSize;
|
||||
table.Emplace<DmTargetLinear>(cow_partition_sectors, cow_image_sectors, cow_image_device,
|
||||
0);
|
||||
}
|
||||
|
||||
// We have created the DmTable now. Map it.
|
||||
std::string cow_path;
|
||||
if (!dm.CreateDevice(*cow_name, table, &cow_path, remaining_time)) {
|
||||
LOG(ERROR) << "Could not create COW device: " << *cow_name;
|
||||
return false;
|
||||
}
|
||||
created_devices->EmplaceBack<AutoUnmapDevice>(&dm, *cow_name);
|
||||
LOG(INFO) << "Mapped COW device for " << params.GetPartitionName() << " at " << cow_path;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SnapshotManager::UnmapCowDevices(LockedFile* lock, const std::string& name) {
|
||||
CHECK(lock);
|
||||
if (!EnsureImageManager()) return false;
|
||||
|
||||
auto& dm = DeviceMapper::Instance();
|
||||
auto cow_name = GetCowName(name);
|
||||
if (!dm.DeleteDeviceIfExists(cow_name)) {
|
||||
LOG(ERROR) << "Cannot unmap " << cow_name;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string cow_image_name = GetCowImageDeviceName(name);
|
||||
if (!images_->UnmapImageIfExists(cow_image_name)) {
|
||||
LOG(ERROR) << "Cannot unmap image " << cow_image_name;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
auto SnapshotManager::OpenFile(const std::string& file, int open_flags, int lock_flags)
|
||||
-> std::unique_ptr<LockedFile> {
|
||||
unique_fd fd(open(file.c_str(), open_flags | O_CLOEXEC | O_NOFOLLOW | O_SYNC, 0660));
|
||||
|
@ -1605,5 +1713,200 @@ bool SnapshotManager::ForceLocalImageManager() {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool SnapshotManager::CreateUpdateSnapshots(MetadataBuilder* target_metadata,
|
||||
const std::string& target_suffix,
|
||||
MetadataBuilder* current_metadata,
|
||||
const std::string& current_suffix,
|
||||
const std::map<std::string, uint64_t>& cow_sizes) {
|
||||
auto lock = LockExclusive();
|
||||
if (!lock) return false;
|
||||
|
||||
// Add _{target_suffix} to COW size map.
|
||||
std::map<std::string, uint64_t> suffixed_cow_sizes;
|
||||
for (const auto& [name, size] : cow_sizes) {
|
||||
suffixed_cow_sizes[name + target_suffix] = size;
|
||||
}
|
||||
|
||||
target_metadata->RemoveGroupAndPartitions(kCowGroupName);
|
||||
if (!target_metadata->AddGroup(kCowGroupName, 0)) {
|
||||
LOG(ERROR) << "Cannot add group " << kCowGroupName;
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO(b/134949511): remove this check. Right now, with overlayfs mounted, the scratch
|
||||
// partition takes up a big chunk of space in super, causing COW images to be created on
|
||||
// retrofit Virtual A/B devices.
|
||||
if (device_->IsOverlayfsSetup()) {
|
||||
LOG(ERROR) << "Cannot create update snapshots with overlayfs setup. Run `adb enable-verity`"
|
||||
<< ", reboot, then try again.";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check that all these metadata is not retrofit dynamic partitions. Snapshots on
|
||||
// devices with retrofit dynamic partitions does not make sense.
|
||||
// This ensures that current_metadata->GetFreeRegions() uses the same device
|
||||
// indices as target_metadata (i.e. 0 -> "super").
|
||||
// This is also assumed in MapCowDevices() call below.
|
||||
CHECK(current_metadata->GetBlockDevicePartitionName(0) == LP_METADATA_DEFAULT_PARTITION_NAME &&
|
||||
target_metadata->GetBlockDevicePartitionName(0) == LP_METADATA_DEFAULT_PARTITION_NAME);
|
||||
|
||||
std::map<std::string, SnapshotStatus> all_snapshot_status;
|
||||
|
||||
// In case of error, automatically delete devices that are created along the way.
|
||||
// Note that "lock" is destroyed after "created_devices", so it is safe to use |lock| for
|
||||
// these devices.
|
||||
AutoDeviceList created_devices;
|
||||
|
||||
for (auto* target_partition : ListPartitionsWithSuffix(target_metadata, target_suffix)) {
|
||||
std::optional<uint64_t> cow_size = std::nullopt;
|
||||
auto it = suffixed_cow_sizes.find(target_partition->name());
|
||||
if (it != suffixed_cow_sizes.end()) {
|
||||
cow_size = it->second;
|
||||
LOG(INFO) << "Using provided COW size " << *cow_size << " for partition "
|
||||
<< target_partition->name();
|
||||
}
|
||||
|
||||
// Compute the device sizes for the partition.
|
||||
PartitionCowCreator cow_creator{target_metadata, target_suffix, target_partition,
|
||||
current_metadata, current_suffix, cow_size};
|
||||
auto cow_creator_ret = cow_creator.Run();
|
||||
if (!cow_creator_ret.has_value()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG(INFO) << "For partition " << target_partition->name()
|
||||
<< ", device size = " << cow_creator_ret->snapshot_status.device_size
|
||||
<< ", snapshot size = " << cow_creator_ret->snapshot_status.snapshot_size
|
||||
<< ", cow partition size = "
|
||||
<< cow_creator_ret->snapshot_status.cow_partition_size
|
||||
<< ", cow file size = " << cow_creator_ret->snapshot_status.cow_file_size;
|
||||
|
||||
// Delete any existing snapshot before re-creating one.
|
||||
if (!DeleteSnapshot(lock.get(), target_partition->name())) {
|
||||
LOG(ERROR) << "Cannot delete existing snapshot before creating a new one for partition "
|
||||
<< target_partition->name();
|
||||
return false;
|
||||
}
|
||||
|
||||
// It is possible that the whole partition uses free space in super, and snapshot / COW
|
||||
// would not be needed. In this case, skip the partition.
|
||||
bool needs_snapshot = cow_creator_ret->snapshot_status.snapshot_size > 0;
|
||||
bool needs_cow = (cow_creator_ret->snapshot_status.cow_partition_size +
|
||||
cow_creator_ret->snapshot_status.cow_file_size) > 0;
|
||||
CHECK(needs_snapshot == needs_cow);
|
||||
|
||||
if (!needs_snapshot) {
|
||||
LOG(INFO) << "Skip creating snapshot for partition " << target_partition->name()
|
||||
<< "because nothing needs to be snapshotted.";
|
||||
continue;
|
||||
}
|
||||
|
||||
// Store these device sizes to snapshot status file.
|
||||
if (!CreateSnapshot(lock.get(), target_partition->name(),
|
||||
cow_creator_ret->snapshot_status)) {
|
||||
return false;
|
||||
}
|
||||
created_devices.EmplaceBack<AutoDeleteSnapshot>(this, lock.get(), target_partition->name());
|
||||
|
||||
// Create the COW partition. That is, use any remaining free space in super partition before
|
||||
// creating the COW images.
|
||||
if (cow_creator_ret->snapshot_status.cow_partition_size > 0) {
|
||||
CHECK(cow_creator_ret->snapshot_status.cow_partition_size % kSectorSize == 0)
|
||||
<< "cow_partition_size == "
|
||||
<< cow_creator_ret->snapshot_status.cow_partition_size
|
||||
<< " is not a multiple of sector size " << kSectorSize;
|
||||
auto cow_partition = target_metadata->AddPartition(GetCowName(target_partition->name()),
|
||||
kCowGroupName, 0 /* flags */);
|
||||
if (cow_partition == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!target_metadata->ResizePartition(
|
||||
cow_partition, cow_creator_ret->snapshot_status.cow_partition_size,
|
||||
cow_creator_ret->cow_partition_usable_regions)) {
|
||||
LOG(ERROR) << "Cannot create COW partition on metadata with size "
|
||||
<< cow_creator_ret->snapshot_status.cow_partition_size;
|
||||
return false;
|
||||
}
|
||||
// Only the in-memory target_metadata is modified; nothing to clean up if there is an
|
||||
// error in the future.
|
||||
}
|
||||
|
||||
// Create the backing COW image if necessary.
|
||||
if (cow_creator_ret->snapshot_status.cow_file_size > 0) {
|
||||
if (!CreateCowImage(lock.get(), target_partition->name())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
all_snapshot_status[target_partition->name()] = std::move(cow_creator_ret->snapshot_status);
|
||||
|
||||
LOG(INFO) << "Successfully created snapshot for " << target_partition->name();
|
||||
}
|
||||
|
||||
auto& dm = DeviceMapper::Instance();
|
||||
auto exported_target_metadata = target_metadata->Export();
|
||||
CreateLogicalPartitionParams cow_params{
|
||||
.block_device = LP_METADATA_DEFAULT_PARTITION_NAME,
|
||||
.metadata = exported_target_metadata.get(),
|
||||
.timeout_ms = std::chrono::milliseconds::max(),
|
||||
.partition_opener = &device_->GetPartitionOpener(),
|
||||
};
|
||||
for (auto* target_partition : ListPartitionsWithSuffix(target_metadata, target_suffix)) {
|
||||
AutoDeviceList created_devices_for_cow;
|
||||
|
||||
if (!UnmapPartitionWithSnapshot(lock.get(), target_partition->name())) {
|
||||
LOG(ERROR) << "Cannot unmap existing COW devices before re-mapping them for zero-fill: "
|
||||
<< target_partition->name();
|
||||
return false;
|
||||
}
|
||||
|
||||
auto it = all_snapshot_status.find(target_partition->name());
|
||||
CHECK(it != all_snapshot_status.end()) << target_partition->name();
|
||||
cow_params.partition_name = target_partition->name();
|
||||
std::string cow_name;
|
||||
if (!MapCowDevices(lock.get(), cow_params, it->second, &created_devices_for_cow,
|
||||
&cow_name)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string cow_path;
|
||||
if (!dm.GetDmDevicePathByName(cow_name, &cow_path)) {
|
||||
LOG(ERROR) << "Cannot determine path for " << cow_name;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!InitializeCow(cow_path)) {
|
||||
LOG(ERROR) << "Can't zero-fill COW device for " << target_partition->name() << ": "
|
||||
<< cow_path;
|
||||
return false;
|
||||
}
|
||||
// Let destructor of created_devices_for_cow to unmap the COW devices.
|
||||
};
|
||||
|
||||
created_devices.Release();
|
||||
LOG(INFO) << "Successfully created all snapshots for target slot " << target_suffix;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SnapshotManager::MapUpdateSnapshot(const CreateLogicalPartitionParams& params,
|
||||
std::string* snapshot_path) {
|
||||
auto lock = LockShared();
|
||||
if (!lock) return false;
|
||||
if (!UnmapPartitionWithSnapshot(lock.get(), params.GetPartitionName())) {
|
||||
LOG(ERROR) << "Cannot unmap existing snapshot before re-mapping it: "
|
||||
<< params.GetPartitionName();
|
||||
return false;
|
||||
}
|
||||
return MapPartitionWithSnapshot(lock.get(), params, snapshot_path);
|
||||
}
|
||||
|
||||
bool SnapshotManager::UnmapUpdateSnapshot(const std::string& target_partition_name) {
|
||||
auto lock = LockShared();
|
||||
if (!lock) return false;
|
||||
return UnmapPartitionWithSnapshot(lock.get(), target_partition_name);
|
||||
}
|
||||
|
||||
} // namespace snapshot
|
||||
} // namespace android
|
||||
|
|
|
@ -33,7 +33,9 @@
|
|||
#include <liblp/builder.h>
|
||||
#include <liblp/mock_property_fetcher.h>
|
||||
|
||||
#include "digital_storage.h"
|
||||
#include "test_helpers.h"
|
||||
#include "utility.h"
|
||||
|
||||
namespace android {
|
||||
namespace snapshot {
|
||||
|
@ -45,10 +47,12 @@ using android::fiemap::IImageManager;
|
|||
using android::fs_mgr::BlockDeviceInfo;
|
||||
using android::fs_mgr::CreateLogicalPartitionParams;
|
||||
using android::fs_mgr::DestroyLogicalPartition;
|
||||
using android::fs_mgr::GetPartitionGroupName;
|
||||
using android::fs_mgr::GetPartitionName;
|
||||
using android::fs_mgr::MetadataBuilder;
|
||||
using namespace ::testing;
|
||||
using namespace android::fs_mgr::testing;
|
||||
using namespace android::digital_storage;
|
||||
using namespace std::chrono_literals;
|
||||
using namespace std::string_literals;
|
||||
|
||||
|
@ -58,7 +62,7 @@ std::unique_ptr<SnapshotManager> sm;
|
|||
TestDeviceInfo* test_device = nullptr;
|
||||
std::string fake_super;
|
||||
|
||||
static constexpr uint64_t kSuperSize = 16 * 1024 * 1024;
|
||||
static constexpr uint64_t kSuperSize = (16_MiB).bytes();
|
||||
|
||||
class SnapshotTest : public ::testing::Test {
|
||||
public:
|
||||
|
@ -105,7 +109,7 @@ class SnapshotTest : public ::testing::Test {
|
|||
std::vector<std::string> snapshots = {"test-snapshot", "test_partition_a",
|
||||
"test_partition_b"};
|
||||
for (const auto& snapshot : snapshots) {
|
||||
DeleteSnapshotDevice(snapshot);
|
||||
ASSERT_TRUE(DeleteSnapshotDevice(snapshot));
|
||||
DeleteBackingImage(image_manager_, snapshot + "-cow-img");
|
||||
|
||||
auto status_file = sm->GetSnapshotStatusFilePath(snapshot);
|
||||
|
@ -211,15 +215,52 @@ class SnapshotTest : public ::testing::Test {
|
|||
return true;
|
||||
}
|
||||
|
||||
void DeleteSnapshotDevice(const std::string& snapshot) {
|
||||
DeleteDevice(snapshot);
|
||||
DeleteDevice(snapshot + "-inner");
|
||||
ASSERT_TRUE(image_manager_->UnmapImageIfExists(snapshot + "-cow-img"));
|
||||
}
|
||||
void DeleteDevice(const std::string& device) {
|
||||
if (dm_.GetState(device) != DmDeviceState::INVALID) {
|
||||
ASSERT_TRUE(dm_.DeleteDevice(device));
|
||||
AssertionResult DeleteSnapshotDevice(const std::string& snapshot) {
|
||||
AssertionResult res = AssertionSuccess();
|
||||
if (!(res = DeleteDevice(snapshot))) return res;
|
||||
if (!(res = DeleteDevice(snapshot + "-inner"))) return res;
|
||||
if (!(res = DeleteDevice(snapshot + "-cow"))) return res;
|
||||
if (!image_manager_->UnmapImageIfExists(snapshot + "-cow-img")) {
|
||||
return AssertionFailure() << "Cannot unmap image " << snapshot << "-cow-img";
|
||||
}
|
||||
if (!(res = DeleteDevice(snapshot + "-base"))) return res;
|
||||
return AssertionSuccess();
|
||||
}
|
||||
|
||||
AssertionResult DeleteDevice(const std::string& device) {
|
||||
if (!dm_.DeleteDeviceIfExists(device)) {
|
||||
return AssertionFailure() << "Can't delete " << device;
|
||||
}
|
||||
return AssertionSuccess();
|
||||
}
|
||||
|
||||
AssertionResult CreateCowImage(const std::string& name) {
|
||||
if (!sm->CreateCowImage(lock_.get(), name)) {
|
||||
return AssertionFailure() << "Cannot create COW image " << name;
|
||||
}
|
||||
std::string cow_device;
|
||||
auto map_res = MapCowImage(name, 10s, &cow_device);
|
||||
if (!map_res) {
|
||||
return map_res;
|
||||
}
|
||||
if (!InitializeCow(cow_device)) {
|
||||
return AssertionFailure() << "Cannot zero fill " << cow_device;
|
||||
}
|
||||
if (!sm->UnmapCowImage(name)) {
|
||||
return AssertionFailure() << "Cannot unmap " << name << " after zero filling it";
|
||||
}
|
||||
return AssertionSuccess();
|
||||
}
|
||||
|
||||
AssertionResult MapCowImage(const std::string& name,
|
||||
const std::chrono::milliseconds& timeout_ms, std::string* path) {
|
||||
if (!sm->MapCowImage(name, timeout_ms)) {
|
||||
return AssertionFailure() << "Cannot map cow image " << name;
|
||||
}
|
||||
if (!dm_.GetDmDevicePathByName(name + "-cow-img"s, path)) {
|
||||
return AssertionFailure() << "No path for " << name << "-cow-img";
|
||||
}
|
||||
return AssertionSuccess();
|
||||
}
|
||||
|
||||
DeviceMapper& dm_;
|
||||
|
@ -236,7 +277,7 @@ TEST_F(SnapshotTest, CreateSnapshot) {
|
|||
{.device_size = kDeviceSize,
|
||||
.snapshot_size = kDeviceSize,
|
||||
.cow_file_size = kDeviceSize}));
|
||||
ASSERT_TRUE(sm->CreateCowImage(lock_.get(), "test-snapshot"));
|
||||
ASSERT_TRUE(CreateCowImage("test-snapshot"));
|
||||
|
||||
std::vector<std::string> snapshots;
|
||||
ASSERT_TRUE(sm->ListSnapshots(lock_.get(), &snapshots));
|
||||
|
@ -265,13 +306,13 @@ TEST_F(SnapshotTest, MapSnapshot) {
|
|||
{.device_size = kDeviceSize,
|
||||
.snapshot_size = kDeviceSize,
|
||||
.cow_file_size = kDeviceSize}));
|
||||
ASSERT_TRUE(sm->CreateCowImage(lock_.get(), "test-snapshot"));
|
||||
ASSERT_TRUE(CreateCowImage("test-snapshot"));
|
||||
|
||||
std::string base_device;
|
||||
ASSERT_TRUE(CreatePartition("base-device", kDeviceSize, &base_device));
|
||||
|
||||
std::string cow_device;
|
||||
ASSERT_TRUE(sm->MapCowImage("test-snapshot", 10s, &cow_device));
|
||||
ASSERT_TRUE(MapCowImage("test-snapshot", 10s, &cow_device));
|
||||
|
||||
std::string snap_device;
|
||||
ASSERT_TRUE(sm->MapSnapshot(lock_.get(), "test-snapshot", base_device, cow_device, 10s,
|
||||
|
@ -288,13 +329,13 @@ TEST_F(SnapshotTest, MapPartialSnapshot) {
|
|||
{.device_size = kDeviceSize,
|
||||
.snapshot_size = kSnapshotSize,
|
||||
.cow_file_size = kSnapshotSize}));
|
||||
ASSERT_TRUE(sm->CreateCowImage(lock_.get(), "test-snapshot"));
|
||||
ASSERT_TRUE(CreateCowImage("test-snapshot"));
|
||||
|
||||
std::string base_device;
|
||||
ASSERT_TRUE(CreatePartition("base-device", kDeviceSize, &base_device));
|
||||
|
||||
std::string cow_device;
|
||||
ASSERT_TRUE(sm->MapCowImage("test-snapshot", 10s, &cow_device));
|
||||
ASSERT_TRUE(MapCowImage("test-snapshot", 10s, &cow_device));
|
||||
|
||||
std::string snap_device;
|
||||
ASSERT_TRUE(sm->MapSnapshot(lock_.get(), "test-snapshot", base_device, cow_device, 10s,
|
||||
|
@ -344,8 +385,8 @@ TEST_F(SnapshotTest, Merge) {
|
|||
{.device_size = kDeviceSize,
|
||||
.snapshot_size = kDeviceSize,
|
||||
.cow_file_size = kDeviceSize}));
|
||||
ASSERT_TRUE(sm->CreateCowImage(lock_.get(), "test_partition_b"));
|
||||
ASSERT_TRUE(sm->MapCowImage("test_partition_b", 10s, &cow_device));
|
||||
ASSERT_TRUE(CreateCowImage("test_partition_b"));
|
||||
ASSERT_TRUE(MapCowImage("test_partition_b", 10s, &cow_device));
|
||||
ASSERT_TRUE(sm->MapSnapshot(lock_.get(), "test_partition_b", base_device, cow_device, 10s,
|
||||
&snap_device));
|
||||
|
||||
|
@ -403,11 +444,11 @@ TEST_F(SnapshotTest, MergeCannotRemoveCow) {
|
|||
{.device_size = kDeviceSize,
|
||||
.snapshot_size = kDeviceSize,
|
||||
.cow_file_size = kDeviceSize}));
|
||||
ASSERT_TRUE(sm->CreateCowImage(lock_.get(), "test-snapshot"));
|
||||
ASSERT_TRUE(CreateCowImage("test-snapshot"));
|
||||
|
||||
std::string base_device, cow_device, snap_device;
|
||||
ASSERT_TRUE(CreatePartition("base-device", kDeviceSize, &base_device));
|
||||
ASSERT_TRUE(sm->MapCowImage("test-snapshot", 10s, &cow_device));
|
||||
ASSERT_TRUE(MapCowImage("test-snapshot", 10s, &cow_device));
|
||||
ASSERT_TRUE(sm->MapSnapshot(lock_.get(), "test-snapshot", base_device, cow_device, 10s,
|
||||
&snap_device));
|
||||
|
||||
|
@ -433,11 +474,11 @@ TEST_F(SnapshotTest, MergeCannotRemoveCow) {
|
|||
// Wait 1s, otherwise DeleteSnapshotDevice may fail with EBUSY.
|
||||
sleep(1);
|
||||
// Forcefully delete the snapshot device, so it looks like we just rebooted.
|
||||
DeleteSnapshotDevice("test-snapshot");
|
||||
ASSERT_TRUE(DeleteSnapshotDevice("test-snapshot"));
|
||||
|
||||
// Map snapshot should fail now, because we're in a merge-complete state.
|
||||
ASSERT_TRUE(AcquireLock());
|
||||
ASSERT_TRUE(sm->MapCowImage("test-snapshot", 10s, &cow_device));
|
||||
ASSERT_TRUE(MapCowImage("test-snapshot", 10s, &cow_device));
|
||||
ASSERT_FALSE(sm->MapSnapshot(lock_.get(), "test-snapshot", base_device, cow_device, 10s,
|
||||
&snap_device));
|
||||
|
||||
|
@ -462,7 +503,7 @@ TEST_F(SnapshotTest, FirstStageMountAndMerge) {
|
|||
{.device_size = kDeviceSize,
|
||||
.snapshot_size = kDeviceSize,
|
||||
.cow_file_size = kDeviceSize}));
|
||||
ASSERT_TRUE(sm->CreateCowImage(lock_.get(), "test_partition_b"));
|
||||
ASSERT_TRUE(CreateCowImage("test_partition_b"));
|
||||
|
||||
// Simulate a reboot into the new slot.
|
||||
lock_ = nullptr;
|
||||
|
@ -504,7 +545,7 @@ TEST_F(SnapshotTest, FlashSuperDuringUpdate) {
|
|||
{.device_size = kDeviceSize,
|
||||
.snapshot_size = kDeviceSize,
|
||||
.cow_file_size = kDeviceSize}));
|
||||
ASSERT_TRUE(sm->CreateCowImage(lock_.get(), "test_partition_b"));
|
||||
ASSERT_TRUE(CreateCowImage("test_partition_b"));
|
||||
|
||||
// Simulate a reboot into the new slot.
|
||||
lock_ = nullptr;
|
||||
|
@ -552,7 +593,7 @@ TEST_F(SnapshotTest, FlashSuperDuringMerge) {
|
|||
{.device_size = kDeviceSize,
|
||||
.snapshot_size = kDeviceSize,
|
||||
.cow_file_size = kDeviceSize}));
|
||||
ASSERT_TRUE(sm->CreateCowImage(lock_.get(), "test_partition_b"));
|
||||
ASSERT_TRUE(CreateCowImage("test_partition_b"));
|
||||
|
||||
// Simulate a reboot into the new slot.
|
||||
lock_ = nullptr;
|
||||
|
@ -570,7 +611,7 @@ TEST_F(SnapshotTest, FlashSuperDuringMerge) {
|
|||
|
||||
// Now, reflash super. Note that we haven't called ProcessUpdateState, so the
|
||||
// status is still Merging.
|
||||
DeleteSnapshotDevice("test_partition_b");
|
||||
ASSERT_TRUE(DeleteSnapshotDevice("test_partition_b"));
|
||||
ASSERT_TRUE(init->image_manager()->UnmapImageIfExists("test_partition_b-cow-img"));
|
||||
FormatFakeSuper();
|
||||
ASSERT_TRUE(CreatePartition("test_partition_b", kDeviceSize));
|
||||
|
@ -583,6 +624,419 @@ TEST_F(SnapshotTest, FlashSuperDuringMerge) {
|
|||
ASSERT_EQ(sm->GetUpdateState(), UpdateState::None);
|
||||
}
|
||||
|
||||
class SnapshotUpdateTest : public SnapshotTest {
|
||||
public:
|
||||
void SetUp() override {
|
||||
SnapshotTest::SetUp();
|
||||
Cleanup();
|
||||
|
||||
// Cleanup() changes slot suffix, so initialize it again.
|
||||
test_device->set_slot_suffix("_a");
|
||||
|
||||
ON_CALL(*GetMockedPropertyFetcher(), GetBoolProperty("ro.virtual_ab.enabled", _))
|
||||
.WillByDefault(Return(true));
|
||||
|
||||
opener = std::make_unique<TestPartitionOpener>(fake_super);
|
||||
|
||||
// Initialize source partition metadata. sys_b is similar to system_other.
|
||||
// Not using full name "system", "vendor", "product" because these names collide with the
|
||||
// mapped partitions on the running device.
|
||||
src = MetadataBuilder::New(*opener, "super", 0);
|
||||
ASSERT_NE(nullptr, src);
|
||||
auto partition = src->AddPartition("sys_a", 0);
|
||||
ASSERT_NE(nullptr, partition);
|
||||
ASSERT_TRUE(src->ResizePartition(partition, 3_MiB));
|
||||
partition = src->AddPartition("vnd_a", 0);
|
||||
ASSERT_NE(nullptr, partition);
|
||||
ASSERT_TRUE(src->ResizePartition(partition, 3_MiB));
|
||||
partition = src->AddPartition("prd_a", 0);
|
||||
ASSERT_NE(nullptr, partition);
|
||||
ASSERT_TRUE(src->ResizePartition(partition, 3_MiB));
|
||||
partition = src->AddPartition("sys_b", 0);
|
||||
ASSERT_NE(nullptr, partition);
|
||||
ASSERT_TRUE(src->ResizePartition(partition, 1_MiB));
|
||||
auto metadata = src->Export();
|
||||
ASSERT_NE(nullptr, metadata);
|
||||
ASSERT_TRUE(UpdatePartitionTable(*opener, "super", *metadata.get(), 0));
|
||||
|
||||
// Map source partitions. Additionally, map sys_b to simulate system_other after flashing.
|
||||
std::string path;
|
||||
for (const auto& name : {"sys_a", "vnd_a", "prd_a", "sys_b"}) {
|
||||
ASSERT_TRUE(CreateLogicalPartition(
|
||||
CreateLogicalPartitionParams{
|
||||
.block_device = fake_super,
|
||||
.metadata_slot = 0,
|
||||
.partition_name = name,
|
||||
.timeout_ms = 1s,
|
||||
.partition_opener = opener.get(),
|
||||
},
|
||||
&path));
|
||||
ASSERT_TRUE(WriteRandomData(path));
|
||||
auto hash = GetHash(path);
|
||||
ASSERT_TRUE(hash.has_value());
|
||||
hashes_[name] = *hash;
|
||||
}
|
||||
}
|
||||
void TearDown() override {
|
||||
Cleanup();
|
||||
SnapshotTest::TearDown();
|
||||
}
|
||||
void Cleanup() {
|
||||
if (!image_manager_) {
|
||||
InitializeState();
|
||||
}
|
||||
for (const auto& suffix : {"_a", "_b"}) {
|
||||
test_device->set_slot_suffix(suffix);
|
||||
EXPECT_TRUE(sm->CancelUpdate()) << suffix;
|
||||
}
|
||||
EXPECT_TRUE(UnmapAll());
|
||||
}
|
||||
|
||||
static AssertionResult ResizePartitions(
|
||||
MetadataBuilder* builder,
|
||||
const std::vector<std::pair<std::string, uint64_t>>& partition_sizes) {
|
||||
for (auto&& [name, size] : partition_sizes) {
|
||||
auto partition = builder->FindPartition(name);
|
||||
if (!partition) {
|
||||
return AssertionFailure() << "Cannot find partition in metadata " << name;
|
||||
}
|
||||
if (!builder->ResizePartition(partition, size)) {
|
||||
return AssertionFailure()
|
||||
<< "Cannot resize partition " << name << " to " << size << " bytes";
|
||||
}
|
||||
}
|
||||
return AssertionSuccess();
|
||||
}
|
||||
|
||||
AssertionResult IsPartitionUnchanged(const std::string& name) {
|
||||
std::string path;
|
||||
if (!dm_.GetDmDevicePathByName(name, &path)) {
|
||||
return AssertionFailure() << "Path of " << name << " cannot be determined";
|
||||
}
|
||||
auto hash = GetHash(path);
|
||||
if (!hash.has_value()) {
|
||||
return AssertionFailure() << "Cannot read partition " << name << ": " << path;
|
||||
}
|
||||
if (hashes_[name] != *hash) {
|
||||
return AssertionFailure() << "Content of " << name << " has changed after the merge";
|
||||
}
|
||||
return AssertionSuccess();
|
||||
}
|
||||
|
||||
std::optional<uint64_t> GetSnapshotSize(const std::string& name) {
|
||||
if (!AcquireLock()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
auto local_lock = std::move(lock_);
|
||||
|
||||
SnapshotManager::SnapshotStatus status;
|
||||
if (!sm->ReadSnapshotStatus(local_lock.get(), name, &status)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return status.snapshot_size;
|
||||
}
|
||||
|
||||
AssertionResult UnmapAll() {
|
||||
for (const auto& name : {"sys", "vnd", "prd"}) {
|
||||
if (!dm_.DeleteDeviceIfExists(name + "_a"s)) {
|
||||
return AssertionFailure() << "Cannot unmap " << name << "_a";
|
||||
}
|
||||
if (!DeleteSnapshotDevice(name + "_b"s)) {
|
||||
return AssertionFailure() << "Cannot delete snapshot " << name << "_b";
|
||||
}
|
||||
}
|
||||
return AssertionSuccess();
|
||||
}
|
||||
|
||||
std::unique_ptr<TestPartitionOpener> opener;
|
||||
std::unique_ptr<MetadataBuilder> src;
|
||||
std::map<std::string, std::string> hashes_;
|
||||
};
|
||||
|
||||
// Test full update flow executed by update_engine. Some partitions uses super empty space,
|
||||
// some uses images, and some uses both.
|
||||
// Also test UnmapUpdateSnapshot unmaps everything.
|
||||
// Also test first stage mount and merge after this.
|
||||
TEST_F(SnapshotUpdateTest, FullUpdateFlow) {
|
||||
// OTA client calls CancelUpdate then BeginUpdate before doing anything.
|
||||
ASSERT_TRUE(sm->CancelUpdate());
|
||||
ASSERT_TRUE(sm->BeginUpdate());
|
||||
|
||||
// OTA client blindly unmaps all partitions that are possibly mapped.
|
||||
for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
|
||||
ASSERT_TRUE(sm->UnmapUpdateSnapshot(name));
|
||||
}
|
||||
|
||||
// OTA client adjusts the partition sizes before giving it to CreateUpdateSnapshots.
|
||||
auto tgt = MetadataBuilder::NewForUpdate(*opener, "super", 0, 1);
|
||||
ASSERT_NE(nullptr, tgt);
|
||||
// clang-format off
|
||||
ASSERT_TRUE(ResizePartitions(tgt.get(), {
|
||||
{"sys_b", 4_MiB}, // grows
|
||||
{"vnd_b", 4_MiB}, // grows
|
||||
{"prd_b", 4_MiB}, // grows
|
||||
}));
|
||||
// clang-format on
|
||||
|
||||
ASSERT_TRUE(sm->CreateUpdateSnapshots(tgt.get(), "_b", src.get(), "_a", {}));
|
||||
|
||||
// Test that partitions prioritize using space in super.
|
||||
ASSERT_NE(nullptr, tgt->FindPartition("sys_b-cow"));
|
||||
ASSERT_NE(nullptr, tgt->FindPartition("vnd_b-cow"));
|
||||
ASSERT_EQ(nullptr, tgt->FindPartition("prd_b-cow"));
|
||||
|
||||
// The metadata slot 1 is now updated.
|
||||
auto metadata = tgt->Export();
|
||||
ASSERT_NE(nullptr, metadata);
|
||||
ASSERT_TRUE(UpdatePartitionTable(*opener, "super", *metadata.get(), 1));
|
||||
|
||||
// Write some data to target partitions.
|
||||
for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
|
||||
std::string path;
|
||||
ASSERT_TRUE(sm->MapUpdateSnapshot(
|
||||
CreateLogicalPartitionParams{
|
||||
.block_device = fake_super,
|
||||
.metadata_slot = 1,
|
||||
.partition_name = name,
|
||||
.timeout_ms = 10s,
|
||||
.partition_opener = opener.get(),
|
||||
},
|
||||
&path))
|
||||
<< name;
|
||||
ASSERT_TRUE(WriteRandomData(path));
|
||||
auto hash = GetHash(path);
|
||||
ASSERT_TRUE(hash.has_value());
|
||||
hashes_[name] = *hash;
|
||||
}
|
||||
|
||||
// Assert that source partitions aren't affected.
|
||||
for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) {
|
||||
ASSERT_TRUE(IsPartitionUnchanged(name));
|
||||
}
|
||||
|
||||
ASSERT_TRUE(sm->FinishedSnapshotWrites());
|
||||
|
||||
// Simulate shutting down the device.
|
||||
ASSERT_TRUE(UnmapAll());
|
||||
|
||||
// After reboot, init does first stage mount.
|
||||
auto rebooted = new TestDeviceInfo(fake_super);
|
||||
rebooted->set_slot_suffix("_b");
|
||||
auto init = SnapshotManager::NewForFirstStageMount(rebooted);
|
||||
ASSERT_NE(init, nullptr);
|
||||
ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount());
|
||||
ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super"));
|
||||
|
||||
// Check that the target partitions have the same content.
|
||||
for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
|
||||
ASSERT_TRUE(IsPartitionUnchanged(name));
|
||||
}
|
||||
|
||||
// Initiate the merge and wait for it to be completed.
|
||||
ASSERT_TRUE(init->InitiateMerge());
|
||||
ASSERT_EQ(UpdateState::MergeCompleted, init->ProcessUpdateState());
|
||||
|
||||
// Check that the target partitions have the same content after the merge.
|
||||
for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
|
||||
ASSERT_TRUE(IsPartitionUnchanged(name))
|
||||
<< "Content of " << name << " changes after the merge";
|
||||
}
|
||||
}
|
||||
|
||||
// Test that if new system partitions uses empty space in super, that region is not snapshotted.
|
||||
TEST_F(SnapshotUpdateTest, DirectWriteEmptySpace) {
|
||||
auto tgt = MetadataBuilder::NewForUpdate(*opener, "super", 0, 1);
|
||||
ASSERT_NE(nullptr, tgt);
|
||||
// clang-format off
|
||||
ASSERT_TRUE(ResizePartitions(tgt.get(), {
|
||||
{"sys_b", 4_MiB}, // grows
|
||||
// vnd_b and prd_b are unchanged
|
||||
}));
|
||||
// clang-format on
|
||||
ASSERT_TRUE(sm->CreateUpdateSnapshots(tgt.get(), "_b", src.get(), "_a", {}));
|
||||
|
||||
ASSERT_EQ(3_MiB, GetSnapshotSize("sys_b").value_or(0));
|
||||
}
|
||||
|
||||
// Test that if new system partitions uses space of old vendor partition, that region is
|
||||
// snapshotted.
|
||||
TEST_F(SnapshotUpdateTest, SnapshotOldPartitions) {
|
||||
auto tgt = MetadataBuilder::NewForUpdate(*opener, "super", 0, 1);
|
||||
ASSERT_NE(nullptr, tgt);
|
||||
// clang-format off
|
||||
ASSERT_TRUE(ResizePartitions(tgt.get(), {
|
||||
{"vnd_b", 2_MiB}, // shrinks
|
||||
{"sys_b", 4_MiB}, // grows
|
||||
// prd_b is unchanged
|
||||
}));
|
||||
// clang-format on
|
||||
ASSERT_TRUE(sm->CreateUpdateSnapshots(tgt.get(), "_b", src.get(), "_a", {}));
|
||||
|
||||
ASSERT_EQ(4_MiB, GetSnapshotSize("sys_b").value_or(0));
|
||||
}
|
||||
|
||||
// Test that even if there seem to be empty space in target metadata, COW partition won't take
|
||||
// it because they are used by old partitions.
|
||||
TEST_F(SnapshotUpdateTest, CowPartitionDoNotTakeOldPartitions) {
|
||||
auto tgt = MetadataBuilder::NewForUpdate(*opener, "super", 0, 1);
|
||||
ASSERT_NE(nullptr, tgt);
|
||||
// clang-format off
|
||||
ASSERT_TRUE(ResizePartitions(tgt.get(), {
|
||||
{"sys_b", 2_MiB}, // shrinks
|
||||
// vnd_b and prd_b are unchanged
|
||||
}));
|
||||
// clang-format on
|
||||
ASSERT_TRUE(sm->CreateUpdateSnapshots(tgt.get(), "_b", src.get(), "_a", {}));
|
||||
|
||||
auto metadata = tgt->Export();
|
||||
ASSERT_NE(nullptr, metadata);
|
||||
std::vector<std::string> written;
|
||||
// Write random data to all COW partitions in super
|
||||
for (auto p : metadata->partitions) {
|
||||
if (GetPartitionGroupName(metadata->groups[p.group_index]) != kCowGroupName) {
|
||||
continue;
|
||||
}
|
||||
std::string path;
|
||||
ASSERT_TRUE(CreateLogicalPartition(
|
||||
CreateLogicalPartitionParams{
|
||||
.block_device = fake_super,
|
||||
.metadata = metadata.get(),
|
||||
.partition = &p,
|
||||
.timeout_ms = 1s,
|
||||
.partition_opener = opener.get(),
|
||||
},
|
||||
&path));
|
||||
ASSERT_TRUE(WriteRandomData(path));
|
||||
written.push_back(GetPartitionName(p));
|
||||
}
|
||||
ASSERT_FALSE(written.empty())
|
||||
<< "No COW partitions are created even if there are empty space in super partition";
|
||||
|
||||
// Make sure source partitions aren't affected.
|
||||
for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) {
|
||||
ASSERT_TRUE(IsPartitionUnchanged(name));
|
||||
}
|
||||
}
|
||||
|
||||
// Test that it crashes after creating snapshot status file but before creating COW image, then
|
||||
// calling CreateUpdateSnapshots again works.
|
||||
TEST_F(SnapshotUpdateTest, SnapshotStatusFileWithoutCow) {
|
||||
// Write some trash snapshot files to simulate leftovers from previous runs.
|
||||
{
|
||||
ASSERT_TRUE(AcquireLock());
|
||||
auto local_lock = std::move(lock_);
|
||||
ASSERT_TRUE(sm->WriteSnapshotStatus(local_lock.get(), "sys_b",
|
||||
SnapshotManager::SnapshotStatus{}));
|
||||
ASSERT_TRUE(image_manager_->CreateBackingImage("sys_b-cow-img", 1_MiB,
|
||||
IImageManager::CREATE_IMAGE_DEFAULT));
|
||||
}
|
||||
|
||||
// Redo the update.
|
||||
ASSERT_TRUE(sm->BeginUpdate());
|
||||
ASSERT_TRUE(sm->UnmapUpdateSnapshot("sys_b"));
|
||||
auto tgt = MetadataBuilder::NewForUpdate(*opener, "super", 0, 1);
|
||||
ASSERT_NE(nullptr, tgt);
|
||||
ASSERT_TRUE(sm->CreateUpdateSnapshots(tgt.get(), "_b", src.get(), "_a", {}));
|
||||
|
||||
// The metadata slot 1 is now updated.
|
||||
auto metadata = tgt->Export();
|
||||
ASSERT_NE(nullptr, metadata);
|
||||
ASSERT_TRUE(UpdatePartitionTable(*opener, "super", *metadata.get(), 1));
|
||||
|
||||
// Check that target partitions can be mapped.
|
||||
for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
|
||||
std::string path;
|
||||
EXPECT_TRUE(sm->MapUpdateSnapshot(
|
||||
CreateLogicalPartitionParams{
|
||||
.block_device = fake_super,
|
||||
.metadata_slot = 1,
|
||||
.partition_name = name,
|
||||
.timeout_ms = 10s,
|
||||
.partition_opener = opener.get(),
|
||||
},
|
||||
&path))
|
||||
<< name;
|
||||
}
|
||||
}
|
||||
|
||||
// Test that the old partitions are not modified.
|
||||
TEST_F(SnapshotUpdateTest, TestRollback) {
|
||||
// Execute the update.
|
||||
ASSERT_TRUE(sm->BeginUpdate());
|
||||
ASSERT_TRUE(sm->UnmapUpdateSnapshot("sys_b"));
|
||||
auto tgt = MetadataBuilder::NewForUpdate(*opener, "super", 0, 1);
|
||||
ASSERT_NE(nullptr, tgt);
|
||||
ASSERT_TRUE(sm->CreateUpdateSnapshots(tgt.get(), "_b", src.get(), "_a", {}));
|
||||
|
||||
// The metadata slot 1 is now updated.
|
||||
auto metadata = tgt->Export();
|
||||
ASSERT_NE(nullptr, metadata);
|
||||
ASSERT_TRUE(UpdatePartitionTable(*opener, "super", *metadata.get(), 1));
|
||||
|
||||
// Write some data to target partitions.
|
||||
for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
|
||||
std::string path;
|
||||
ASSERT_TRUE(sm->MapUpdateSnapshot(
|
||||
CreateLogicalPartitionParams{
|
||||
.block_device = fake_super,
|
||||
.metadata_slot = 1,
|
||||
.partition_name = name,
|
||||
.timeout_ms = 10s,
|
||||
.partition_opener = opener.get(),
|
||||
},
|
||||
&path))
|
||||
<< name;
|
||||
ASSERT_TRUE(WriteRandomData(path));
|
||||
auto hash = GetHash(path);
|
||||
ASSERT_TRUE(hash.has_value());
|
||||
hashes_[name] = *hash;
|
||||
}
|
||||
|
||||
// Assert that source partitions aren't affected.
|
||||
for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) {
|
||||
ASSERT_TRUE(IsPartitionUnchanged(name));
|
||||
}
|
||||
|
||||
ASSERT_TRUE(sm->FinishedSnapshotWrites());
|
||||
|
||||
// Simulate shutting down the device.
|
||||
ASSERT_TRUE(UnmapAll());
|
||||
|
||||
// After reboot, init does first stage mount.
|
||||
auto rebooted = new TestDeviceInfo(fake_super);
|
||||
rebooted->set_slot_suffix("_b");
|
||||
auto init = SnapshotManager::NewForFirstStageMount(rebooted);
|
||||
ASSERT_NE(init, nullptr);
|
||||
ASSERT_TRUE(init->NeedSnapshotsInFirstStageMount());
|
||||
ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super"));
|
||||
|
||||
// Check that the target partitions have the same content.
|
||||
for (const auto& name : {"sys_b", "vnd_b", "prd_b"}) {
|
||||
ASSERT_TRUE(IsPartitionUnchanged(name));
|
||||
}
|
||||
|
||||
// Simulate shutting down the device again.
|
||||
ASSERT_TRUE(UnmapAll());
|
||||
rebooted = new TestDeviceInfo(fake_super);
|
||||
rebooted->set_slot_suffix("_a");
|
||||
init = SnapshotManager::NewForFirstStageMount(rebooted);
|
||||
ASSERT_NE(init, nullptr);
|
||||
ASSERT_FALSE(init->NeedSnapshotsInFirstStageMount());
|
||||
ASSERT_TRUE(init->CreateLogicalAndSnapshotPartitions("super"));
|
||||
|
||||
// Assert that the source partitions aren't affected.
|
||||
for (const auto& name : {"sys_a", "vnd_a", "prd_a"}) {
|
||||
ASSERT_TRUE(IsPartitionUnchanged(name));
|
||||
}
|
||||
}
|
||||
|
||||
// Test that if an update is applied but not booted into, it can be canceled.
|
||||
TEST_F(SnapshotUpdateTest, CancelAfterApply) {
|
||||
ASSERT_TRUE(sm->BeginUpdate());
|
||||
ASSERT_TRUE(sm->FinishedSnapshotWrites());
|
||||
ASSERT_TRUE(sm->CancelUpdate());
|
||||
}
|
||||
|
||||
} // namespace snapshot
|
||||
} // namespace android
|
||||
|
||||
|
@ -624,6 +1078,7 @@ int main(int argc, char** argv) {
|
|||
}
|
||||
|
||||
// Clean up previous run.
|
||||
SnapshotUpdateTest().Cleanup();
|
||||
SnapshotTest().Cleanup();
|
||||
|
||||
// Use a separate image manager for our fake super partition.
|
||||
|
|
|
@ -14,11 +14,18 @@
|
|||
|
||||
#include "test_helpers.h"
|
||||
|
||||
#include <android-base/file.h>
|
||||
#include <android-base/logging.h>
|
||||
#include <android-base/unique_fd.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <openssl/sha.h>
|
||||
|
||||
namespace android {
|
||||
namespace snapshot {
|
||||
|
||||
using android::base::ReadFully;
|
||||
using android::base::unique_fd;
|
||||
using android::base::WriteFully;
|
||||
using android::fiemap::IImageManager;
|
||||
|
||||
void DeleteBackingImage(IImageManager* manager, const std::string& name) {
|
||||
|
@ -53,5 +60,55 @@ std::string TestPartitionOpener::GetDeviceString(const std::string& partition_na
|
|||
return PartitionOpener::GetDeviceString(partition_name);
|
||||
}
|
||||
|
||||
bool WriteRandomData(const std::string& path) {
|
||||
unique_fd rand(open("/dev/urandom", O_RDONLY));
|
||||
unique_fd fd(open(path.c_str(), O_WRONLY));
|
||||
|
||||
char buf[4096];
|
||||
while (true) {
|
||||
ssize_t n = TEMP_FAILURE_RETRY(read(rand.get(), buf, sizeof(buf)));
|
||||
if (n <= 0) return false;
|
||||
if (!WriteFully(fd.get(), buf, n)) {
|
||||
if (errno == ENOSPC) {
|
||||
return true;
|
||||
}
|
||||
PLOG(ERROR) << "Cannot write " << path;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string ToHexString(const uint8_t* buf, size_t len) {
|
||||
char lookup[] = "0123456789abcdef";
|
||||
std::string out(len * 2 + 1, '\0');
|
||||
char* outp = out.data();
|
||||
for (; len > 0; len--, buf++) {
|
||||
*outp++ = (char)lookup[*buf >> 4];
|
||||
*outp++ = (char)lookup[*buf & 0xf];
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
std::optional<std::string> GetHash(const std::string& path) {
|
||||
unique_fd fd(open(path.c_str(), O_RDONLY));
|
||||
char buf[4096];
|
||||
SHA256_CTX ctx;
|
||||
SHA256_Init(&ctx);
|
||||
while (true) {
|
||||
ssize_t n = TEMP_FAILURE_RETRY(read(fd.get(), buf, sizeof(buf)));
|
||||
if (n < 0) {
|
||||
PLOG(ERROR) << "Cannot read " << path;
|
||||
return std::nullopt;
|
||||
}
|
||||
if (n == 0) {
|
||||
break;
|
||||
}
|
||||
SHA256_Update(&ctx, buf, n);
|
||||
}
|
||||
uint8_t out[32];
|
||||
SHA256_Final(out, &ctx);
|
||||
return ToHexString(out, sizeof(out));
|
||||
}
|
||||
|
||||
} // namespace snapshot
|
||||
} // namespace android
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include <libfiemap/image_manager.h>
|
||||
|
@ -51,6 +52,7 @@ class TestDeviceInfo : public SnapshotManager::IDeviceInfo {
|
|||
const android::fs_mgr::IPartitionOpener& GetPartitionOpener() const override {
|
||||
return *opener_.get();
|
||||
}
|
||||
bool IsOverlayfsSetup() const override { return false; }
|
||||
|
||||
void set_slot_suffix(const std::string& suffix) { slot_suffix_ = suffix; }
|
||||
void set_fake_super(const std::string& path) {
|
||||
|
@ -65,5 +67,10 @@ class TestDeviceInfo : public SnapshotManager::IDeviceInfo {
|
|||
// Helper for error-spam-free cleanup.
|
||||
void DeleteBackingImage(android::fiemap::IImageManager* manager, const std::string& name);
|
||||
|
||||
// Write some random data to the given device. Will write until reaching end of the device.
|
||||
bool WriteRandomData(const std::string& device);
|
||||
|
||||
std::optional<std::string> GetHash(const std::string& path);
|
||||
|
||||
} // namespace snapshot
|
||||
} // namespace android
|
||||
|
|
|
@ -14,9 +14,13 @@
|
|||
|
||||
#include "utility.h"
|
||||
|
||||
#include <android-base/file.h>
|
||||
#include <android-base/logging.h>
|
||||
#include <android-base/strings.h>
|
||||
|
||||
using android::fs_mgr::MetadataBuilder;
|
||||
using android::fs_mgr::Partition;
|
||||
|
||||
namespace android {
|
||||
namespace snapshot {
|
||||
|
||||
|
@ -52,5 +56,58 @@ AutoUnmapImage::~AutoUnmapImage() {
|
|||
}
|
||||
}
|
||||
|
||||
std::vector<Partition*> ListPartitionsWithSuffix(MetadataBuilder* builder,
|
||||
const std::string& suffix) {
|
||||
std::vector<Partition*> ret;
|
||||
for (const auto& group : builder->ListGroups()) {
|
||||
for (auto* partition : builder->ListPartitionsInGroup(group)) {
|
||||
if (!base::EndsWith(partition->name(), suffix)) {
|
||||
continue;
|
||||
}
|
||||
ret.push_back(partition);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
AutoDeleteSnapshot::~AutoDeleteSnapshot() {
|
||||
if (!name_.empty() && !manager_->DeleteSnapshot(lock_, name_)) {
|
||||
LOG(ERROR) << "Failed to auto delete snapshot " << name_;
|
||||
}
|
||||
}
|
||||
|
||||
bool InitializeCow(const std::string& device) {
|
||||
// When the kernel creates a persistent dm-snapshot, it requires a CoW file
|
||||
// to store the modifications. The kernel interface does not specify how
|
||||
// the CoW is used, and there is no standard associated.
|
||||
// By looking at the current implementation, the CoW file is treated as:
|
||||
// - a _NEW_ snapshot if its first 32 bits are zero, so the newly created
|
||||
// dm-snapshot device will look like a perfect copy of the origin device;
|
||||
// - an _EXISTING_ snapshot if the first 32 bits are equal to a
|
||||
// kernel-specified magic number and the CoW file metadata is set as valid,
|
||||
// so it can be used to resume the last state of a snapshot device;
|
||||
// - an _INVALID_ snapshot otherwise.
|
||||
// To avoid zero-filling the whole CoW file when a new dm-snapshot is
|
||||
// created, here we zero-fill only the first 32 bits. This is a temporary
|
||||
// workaround that will be discussed again when the kernel API gets
|
||||
// consolidated.
|
||||
// TODO(b/139202197): Remove this hack once the kernel API is consolidated.
|
||||
constexpr ssize_t kDmSnapZeroFillSize = 4; // 32-bit
|
||||
|
||||
char zeros[kDmSnapZeroFillSize] = {0};
|
||||
android::base::unique_fd fd(open(device.c_str(), O_WRONLY | O_BINARY));
|
||||
if (fd < 0) {
|
||||
PLOG(ERROR) << "Can't open COW device: " << device;
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG(INFO) << "Zero-filling COW device: " << device;
|
||||
if (!android::base::WriteFully(fd, zeros, kDmSnapZeroFillSize)) {
|
||||
PLOG(ERROR) << "Can't zero-fill COW device for " << device;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace snapshot
|
||||
} // namespace android
|
||||
|
|
|
@ -14,11 +14,14 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
#include <android-base/macros.h>
|
||||
#include <libdm/dm.h>
|
||||
#include <libfiemap/image_manager.h>
|
||||
#include <liblp/builder.h>
|
||||
#include <libsnapshot/snapshot.h>
|
||||
|
||||
namespace android {
|
||||
namespace snapshot {
|
||||
|
@ -81,5 +84,27 @@ struct AutoUnmapImage : AutoDevice {
|
|||
android::fiemap::IImageManager* images_ = nullptr;
|
||||
};
|
||||
|
||||
// Automatically deletes a snapshot. |name| should be the name of the partition, e.g. "system_a".
|
||||
// Client is responsible for maintaining the lifetime of |manager| and |lock|.
|
||||
struct AutoDeleteSnapshot : AutoDevice {
|
||||
AutoDeleteSnapshot(SnapshotManager* manager, SnapshotManager::LockedFile* lock,
|
||||
const std::string& name)
|
||||
: AutoDevice(name), manager_(manager), lock_(lock) {}
|
||||
AutoDeleteSnapshot(AutoDeleteSnapshot&& other);
|
||||
~AutoDeleteSnapshot();
|
||||
|
||||
private:
|
||||
DISALLOW_COPY_AND_ASSIGN(AutoDeleteSnapshot);
|
||||
SnapshotManager* manager_ = nullptr;
|
||||
SnapshotManager::LockedFile* lock_ = nullptr;
|
||||
};
|
||||
|
||||
// Return a list of partitions in |builder| with the name ending in |suffix|.
|
||||
std::vector<android::fs_mgr::Partition*> ListPartitionsWithSuffix(
|
||||
android::fs_mgr::MetadataBuilder* builder, const std::string& suffix);
|
||||
|
||||
// Initialize a device before using it as the COW device for a dm-snapshot device.
|
||||
bool InitializeCow(const std::string& device);
|
||||
|
||||
} // namespace snapshot
|
||||
} // namespace android
|
||||
|
|
Loading…
Reference in New Issue