Convert SnapshotStatus to proto

Also, add a "name" field to SnapshotStatus, and delete
the "name" arg from CreateSnapshot / WriteSnapshotStatus.
ReadSnapshotStatus will warn if the name mismatches from
the file name, and auto-correct it.

Test: libsnapshot_test

Change-Id: I725cf39c07684b100b140a8a21ea9d23ab9d2241
This commit is contained in:
Yifan Hong 2019-10-01 13:59:56 -07:00
parent 58ae8d4780
commit 5fcc2b5d71
9 changed files with 275 additions and 229 deletions

View File

@ -49,11 +49,17 @@ cc_defaults {
"libfiemap_headers",
],
export_include_dirs: ["include"],
proto: {
type: "lite",
export_proto_headers: true,
canonical_path_from_root: false,
},
}
filegroup {
name: "libsnapshot_sources",
srcs: [
"android/snapshot/snapshot.proto",
"snapshot.cpp",
"snapshot_metadata_updater.cpp",
"partition_cow_creator.cpp",
@ -132,9 +138,10 @@ cc_binary {
"libbinder",
"libext4_utils",
"libfs_mgr",
"libutils",
"liblog",
"liblp",
"libprotobuf-cpp-lite",
"libutils",
],
init_rc: [
"snapshotctl.rc",

View File

@ -0,0 +1,87 @@
// 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.
syntax = "proto3";
package android.snapshot;
option optimize_for = LITE_RUNTIME;
// Next: 4
enum SnapshotState {
// No snapshot is found.
NONE = 0;
// The snapshot has been created and possibly written to. Rollbacks are
// possible by destroying the snapshot.
CREATED = 1;
// Changes are being merged. No rollbacks are possible beyond this point.
MERGING = 2;
// Changes have been merged, Future reboots may map the base device
// directly.
MERGE_COMPLETED = 3;
}
// Next: 9
message SnapshotStatus {
// Name of the snapshot. This is usually the name of the snapshotted
// logical partition; for example, "system_b".
string name = 1;
SnapshotState state = 2;
// Size of the full (base) device.
uint64 device_size = 3;
// Size of the snapshot. This is the sum of lengths of ranges in the base
// device that needs to be snapshotted during the update.
// This must be less than or equal to |device_size|.
// This value is 0 if no snapshot is needed for this device because
// no changes
uint64 snapshot_size = 4;
// Size of the "COW partition". A COW partition is a special logical
// partition represented in the super partition metadata. This partition and
// the "COW image" form the "COW device" that supports the snapshot device.
//
// When SnapshotManager creates a COW device, it first searches for unused
// blocks in the super partition, and use those before creating the COW
// image if the COW partition is not big enough.
//
// This value is 0 if no space in super is left for the COW partition.
// |cow_partition_size + cow_file_size| must not be zero if |snapshot_size|
// is non-zero.
uint64 cow_partition_size = 5;
// Size of the "COW file", or "COW image". A COW file / image is created
// when the "COW partition" is not big enough to store changes to the
// snapshot device.
//
// This value is 0 if |cow_partition_size| is big enough to hold all changes
// to the snapshot device.
uint64 cow_file_size = 6;
// Sectors allocated for the COW device. Recording this value right after
// the update and before the merge allows us to infer the progress of the
// merge process.
// This is non-zero when |state| == MERGING or MERGE_COMPLETED.
uint64 sectors_allocated = 7;
// Metadata sectors allocated for the COW device. Recording this value right
// before the update and before the merge allows us to infer the progress of
// the merge process.
// This is non-zero when |state| == MERGING or MERGE_COMPLETED.
uint64 metadata_sectors = 8;
}

View File

@ -53,8 +53,9 @@ namespace snapshot {
struct AutoDeleteCowImage;
struct AutoDeleteSnapshot;
struct PartitionCowCreator;
struct AutoDeviceList;
struct PartitionCowCreator;
class SnapshotStatus;
static constexpr const std::string_view kCowGroupName = "cow";
@ -250,22 +251,6 @@ class SnapshotManager final {
std::unique_ptr<LockedFile> OpenFile(const std::string& file, int open_flags, int lock_flags);
bool Truncate(LockedFile* file);
enum class SnapshotState : int { None, Created, Merging, MergeCompleted };
static std::string to_string(SnapshotState state);
// This state is persisted per-snapshot in /metadata/ota/snapshots/.
struct SnapshotStatus {
SnapshotState state = SnapshotState::None;
uint64_t device_size = 0;
uint64_t snapshot_size = 0;
uint64_t cow_partition_size = 0;
uint64_t cow_file_size = 0;
// These are non-zero when merging.
uint64_t sectors_allocated = 0;
uint64_t metadata_sectors = 0;
};
// Create a new snapshot record. This creates the backing COW store and
// persists information needed to map the device. The device can be mapped
// with MapSnapshot().
@ -282,7 +267,7 @@ class SnapshotManager final {
//
// 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);
bool CreateSnapshot(LockedFile* lock, SnapshotStatus* status);
// |name| should be the base partition name (e.g. "system_a"). Create the
// backing COW image using the size previously passed to CreateSnapshot().
@ -363,8 +348,7 @@ class SnapshotManager final {
UpdateState CheckTargetMergeState(LockedFile* lock, const std::string& name);
// Interact with status files under /metadata/ota/snapshots.
bool WriteSnapshotStatus(LockedFile* lock, const std::string& name,
const SnapshotStatus& status);
bool WriteSnapshotStatus(LockedFile* lock, const SnapshotStatus& status);
bool ReadSnapshotStatus(LockedFile* lock, const std::string& name, SnapshotStatus* status);
std::string GetSnapshotStatusFilePath(const std::string& name);

View File

@ -18,6 +18,7 @@
#include <android-base/logging.h>
#include <android/snapshot/snapshot.pb.h>
#include "utility.h"
using android::dm::kSectorSize;
@ -84,13 +85,14 @@ std::optional<PartitionCowCreator::Return> PartitionCowCreator::Run() {
<< "logical_block_size is not power of 2";
Return ret;
ret.snapshot_status.device_size = target_partition->size();
ret.snapshot_status.set_name(target_partition->name());
ret.snapshot_status.set_device_size(target_partition->size());
// TODO(b/141889746): Optimize by using a smaller snapshot. Some ranges in target_partition
// may be written directly.
ret.snapshot_status.snapshot_size = target_partition->size();
ret.snapshot_status.set_snapshot_size(target_partition->size());
auto cow_size = GetCowSize(ret.snapshot_status.snapshot_size);
auto cow_size = GetCowSize(ret.snapshot_status.snapshot_size());
if (!cow_size.has_value()) return std::nullopt;
// Compute regions that are free in both current and target metadata. These are the regions
@ -106,18 +108,20 @@ std::optional<PartitionCowCreator::Return> PartitionCowCreator::Run() {
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);
uint64_t 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);
cow_partition_size &= ~(logical_block_size - 1);
ret.snapshot_status.set_cow_partition_size(cow_partition_size);
// 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;
uint64_t 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);
cow_file_size += kSectorSize - 1;
cow_file_size &= ~(kSectorSize - 1);
ret.snapshot_status.set_cow_file_size(cow_file_size);
return ret;
}

View File

@ -20,8 +20,9 @@
#include <string>
#include <liblp/builder.h>
#include <update_engine/update_metadata.pb.h>
#include <libsnapshot/snapshot.h>
#include <android/snapshot/snapshot.pb.h>
namespace android {
namespace snapshot {
@ -51,7 +52,7 @@ struct PartitionCowCreator {
const RepeatedPtrField<InstallOperation>* operations = nullptr;
struct Return {
SnapshotManager::SnapshotStatus snapshot_status;
SnapshotStatus snapshot_status;
std::vector<Interval> cow_partition_usable_regions;
};

View File

@ -51,8 +51,8 @@ TEST_F(PartitionCowCreatorTest, IntersectSelf) {
.current_suffix = "_a"};
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(40 * 1024, ret->snapshot_status.device_size());
ASSERT_EQ(40 * 1024, ret->snapshot_status.snapshot_size());
}
TEST_F(PartitionCowCreatorTest, Holes) {

View File

@ -38,6 +38,7 @@
#include <libfiemap/image_manager.h>
#include <liblp/liblp.h>
#include <android/snapshot/snapshot.pb.h>
#include "partition_cow_creator.h"
#include "snapshot_metadata_updater.h"
#include "utility.h"
@ -234,42 +235,50 @@ bool SnapshotManager::FinishedSnapshotWrites() {
return WriteUpdateState(lock.get(), UpdateState::Unverified);
}
bool SnapshotManager::CreateSnapshot(LockedFile* lock, const std::string& name,
SnapshotManager::SnapshotStatus status) {
bool SnapshotManager::CreateSnapshot(LockedFile* lock, SnapshotStatus* status) {
CHECK(lock);
CHECK(lock->lock_mode() == LOCK_EX);
CHECK(status);
if (status->name().empty()) {
LOG(ERROR) << "SnapshotStatus has no name.";
return false;
}
// 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 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
<< " device size is not a multiple of the sector size: " << status.device_size;
if (status->device_size() % kSectorSize != 0) {
LOG(ERROR) << "Snapshot " << status->name()
<< " device size is not a multiple of the sector size: "
<< status->device_size();
return false;
}
if (status.snapshot_size % kSectorSize != 0) {
LOG(ERROR) << "Snapshot " << name << " snapshot size is not a multiple of the sector size: "
<< status.snapshot_size;
if (status->snapshot_size() % kSectorSize != 0) {
LOG(ERROR) << "Snapshot " << status->name()
<< " snapshot size is not a multiple of the sector size: "
<< status->snapshot_size();
return false;
}
if (status.cow_partition_size % kSectorSize != 0) {
LOG(ERROR) << "Snapshot " << name
if (status->cow_partition_size() % kSectorSize != 0) {
LOG(ERROR) << "Snapshot " << status->name()
<< " cow partition size is not a multiple of the sector size: "
<< status.cow_partition_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;
if (status->cow_file_size() % kSectorSize != 0) {
LOG(ERROR) << "Snapshot " << status->name()
<< " cow file size is not a multiple of the sector size: "
<< status->cow_file_size();
return false;
}
status.state = SnapshotState::Created;
status.sectors_allocated = 0;
status.metadata_sectors = 0;
status->set_state(SnapshotState::CREATED);
status->set_sectors_allocated(0);
status->set_metadata_sectors(0);
if (!WriteSnapshotStatus(lock, name, status)) {
PLOG(ERROR) << "Could not write snapshot status: " << name;
if (!WriteSnapshotStatus(lock, *status)) {
PLOG(ERROR) << "Could not write snapshot status: " << status->name();
return false;
}
return true;
@ -287,15 +296,15 @@ bool SnapshotManager::CreateCowImage(LockedFile* lock, const std::string& name)
// The COW file size should have been rounded up to the nearest sector in CreateSnapshot.
// Sanity check this.
if (status.cow_file_size % kSectorSize != 0) {
if (status.cow_file_size() % kSectorSize != 0) {
LOG(ERROR) << "Snapshot " << name << " COW file size is not a multiple of the sector size: "
<< status.cow_file_size;
<< status.cow_file_size();
return false;
}
std::string cow_image_name = GetCowImageDeviceName(name);
int cow_flags = IImageManager::CREATE_IMAGE_DEFAULT;
return images_->CreateBackingImage(cow_image_name, status.cow_file_size, cow_flags);
return images_->CreateBackingImage(cow_image_name, status.cow_file_size(), cow_flags);
}
bool SnapshotManager::MapSnapshot(LockedFile* lock, const std::string& name,
@ -309,7 +318,7 @@ bool SnapshotManager::MapSnapshot(LockedFile* lock, const std::string& name,
if (!ReadSnapshotStatus(lock, name, &status)) {
return false;
}
if (status.state == SnapshotState::MergeCompleted) {
if (status.state() == SnapshotState::MERGE_COMPLETED) {
LOG(ERROR) << "Should not create a snapshot device for " << name
<< " after merging has completed.";
return false;
@ -328,24 +337,23 @@ bool SnapshotManager::MapSnapshot(LockedFile* lock, const std::string& name,
PLOG(ERROR) << "Could not determine block device size: " << base_device;
return false;
}
if (status.device_size != dev_size) {
if (status.device_size() != dev_size) {
LOG(ERROR) << "Block device size for " << base_device << " does not match"
<< "(expected " << status.device_size << ", got " << dev_size << ")";
<< "(expected " << status.device_size() << ", got " << dev_size << ")";
return false;
}
}
if (status.device_size % kSectorSize != 0) {
LOG(ERROR) << "invalid blockdev size for " << base_device << ": " << status.device_size;
if (status.device_size() % kSectorSize != 0) {
LOG(ERROR) << "invalid blockdev size for " << base_device << ": " << status.device_size();
return false;
}
if (status.snapshot_size % kSectorSize != 0 || status.snapshot_size > status.device_size) {
LOG(ERROR) << "Invalid snapshot size for " << base_device << ": " << status.snapshot_size;
if (status.snapshot_size() % kSectorSize != 0 ||
status.snapshot_size() > status.device_size()) {
LOG(ERROR) << "Invalid snapshot size for " << base_device << ": " << status.snapshot_size();
return false;
}
uint64_t snapshot_sectors = status.snapshot_size / kSectorSize;
uint64_t linear_sectors = (status.device_size - status.snapshot_size) / kSectorSize;
uint64_t snapshot_sectors = status.snapshot_size() / kSectorSize;
uint64_t linear_sectors = (status.device_size() - status.snapshot_size()) / kSectorSize;
auto& dm = DeviceMapper::Instance();
@ -557,8 +565,9 @@ bool SnapshotManager::SwitchSnapshotToMerge(LockedFile* lock, const std::string&
if (!ReadSnapshotStatus(lock, name, &status)) {
return false;
}
if (status.state != SnapshotState::Created) {
LOG(WARNING) << "Snapshot " << name << " has unexpected state: " << to_string(status.state);
if (status.state() != SnapshotState::CREATED) {
LOG(WARNING) << "Snapshot " << name
<< " has unexpected state: " << SnapshotState_Name(status.state());
}
// After this, we return true because we technically did switch to a merge
@ -568,15 +577,15 @@ bool SnapshotManager::SwitchSnapshotToMerge(LockedFile* lock, const std::string&
return false;
}
status.state = SnapshotState::Merging;
status.set_state(SnapshotState::MERGING);
DmTargetSnapshot::Status dm_status;
if (!QuerySnapshotStatus(dm_name, nullptr, &dm_status)) {
LOG(ERROR) << "Could not query merge status for snapshot: " << dm_name;
}
status.sectors_allocated = dm_status.sectors_allocated;
status.metadata_sectors = dm_status.metadata_sectors;
if (!WriteSnapshotStatus(lock, name, status)) {
status.set_sectors_allocated(dm_status.sectors_allocated);
status.set_metadata_sectors(dm_status.metadata_sectors);
if (!WriteSnapshotStatus(lock, status)) {
LOG(ERROR) << "Could not update status file for snapshot: " << name;
}
return true;
@ -821,7 +830,7 @@ UpdateState SnapshotManager::CheckTargetMergeState(LockedFile* lock, const std::
// rebooted after this check, the device will still be a snapshot-merge
// target. If the have rebooted, the device will now be a linear target,
// and we can try cleanup again.
if (snapshot_status.state == SnapshotState::MergeCompleted) {
if (snapshot_status.state() == SnapshotState::MERGE_COMPLETED) {
// NB: It's okay if this fails now, we gave cleanup our best effort.
OnSnapshotMergeComplete(lock, name, snapshot_status);
return UpdateState::MergeCompleted;
@ -849,7 +858,7 @@ UpdateState SnapshotManager::CheckTargetMergeState(LockedFile* lock, const std::
// These two values are equal when merging is complete.
if (status.sectors_allocated != status.metadata_sectors) {
if (snapshot_status.state == SnapshotState::MergeCompleted) {
if (snapshot_status.state() == SnapshotState::MERGE_COMPLETED) {
LOG(ERROR) << "Snapshot " << name << " is merging after being marked merge-complete.";
return UpdateState::MergeFailed;
}
@ -864,8 +873,8 @@ UpdateState SnapshotManager::CheckTargetMergeState(LockedFile* lock, const std::
// This makes it simpler to reason about the next reboot: no matter what
// part of cleanup failed, first-stage init won't try to create another
// snapshot device for this partition.
snapshot_status.state = SnapshotState::MergeCompleted;
if (!WriteSnapshotStatus(lock, name, snapshot_status)) {
snapshot_status.set_state(SnapshotState::MERGE_COMPLETED);
if (!WriteSnapshotStatus(lock, snapshot_status)) {
return UpdateState::MergeFailed;
}
if (!OnSnapshotMergeComplete(lock, name, snapshot_status)) {
@ -969,10 +978,10 @@ bool SnapshotManager::CollapseSnapshotDevice(const std::string& name,
return false;
}
uint64_t snapshot_sectors = status.snapshot_size / kSectorSize;
if (snapshot_sectors * kSectorSize != status.snapshot_size) {
uint64_t snapshot_sectors = status.snapshot_size() / kSectorSize;
if (snapshot_sectors * kSectorSize != status.snapshot_size()) {
LOG(ERROR) << "Snapshot " << name
<< " size is not sector aligned: " << status.snapshot_size;
<< " size is not sector aligned: " << status.snapshot_size();
return false;
}
@ -1003,7 +1012,7 @@ bool SnapshotManager::CollapseSnapshotDevice(const std::string& name,
<< " sectors, got: " << outer_table[0].spec.length;
return false;
}
uint64_t expected_device_sectors = status.device_size / kSectorSize;
uint64_t expected_device_sectors = status.device_size() / kSectorSize;
uint64_t actual_device_sectors = outer_table[0].spec.length + outer_table[1].spec.length;
if (expected_device_sectors != actual_device_sectors) {
LOG(ERROR) << "Outer device " << name << " should have " << expected_device_sectors
@ -1289,7 +1298,7 @@ bool SnapshotManager::MapPartitionWithSnapshot(LockedFile* lock,
return false;
}
// No live snapshot if merge is completed.
if (live_snapshot_status->state == SnapshotState::MergeCompleted) {
if (live_snapshot_status->state() == SnapshotState::MERGE_COMPLETED) {
live_snapshot_status.reset();
}
} while (0);
@ -1390,7 +1399,7 @@ bool SnapshotManager::MapCowDevices(LockedFile* lock, const CreateLogicalPartiti
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);
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();
@ -1400,7 +1409,7 @@ bool SnapshotManager::MapCowDevices(LockedFile* lock, const CreateLogicalPartiti
auto& dm = DeviceMapper::Instance();
// Map COW image if necessary.
if (snapshot_status.cow_file_size > 0) {
if (snapshot_status.cow_file_size() > 0) {
auto remaining_time = GetRemainingTime(params.timeout_ms, begin);
if (remaining_time.count() < 0) return false;
@ -1411,7 +1420,7 @@ bool SnapshotManager::MapCowDevices(LockedFile* lock, const CreateLogicalPartiti
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) {
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;
@ -1421,7 +1430,7 @@ bool SnapshotManager::MapCowDevices(LockedFile* lock, const CreateLogicalPartiti
auto remaining_time = GetRemainingTime(params.timeout_ms, begin);
if (remaining_time.count() < 0) return false;
CHECK(snapshot_status.cow_partition_size > 0);
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.
@ -1434,14 +1443,14 @@ bool SnapshotManager::MapCowDevices(LockedFile* lock, const CreateLogicalPartiti
return false;
}
// If the COW image exists, append it as the last extent.
if (snapshot_status.cow_file_size > 0) {
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;
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);
}
@ -1602,101 +1611,38 @@ bool SnapshotManager::ReadSnapshotStatus(LockedFile* lock, const std::string& na
return false;
}
std::string contents;
if (!android::base::ReadFdToString(fd, &contents)) {
PLOG(ERROR) << "read failed: " << path;
return false;
}
auto pieces = android::base::Split(contents, " ");
if (pieces.size() != 7) {
LOG(ERROR) << "Invalid status line for snapshot: " << path;
if (!status->ParseFromFileDescriptor(fd.get())) {
PLOG(ERROR) << "Unable to parse " << path << " as SnapshotStatus";
return false;
}
if (pieces[0] == "none") {
status->state = SnapshotState::None;
} else if (pieces[0] == "created") {
status->state = SnapshotState::Created;
} else if (pieces[0] == "merging") {
status->state = SnapshotState::Merging;
} else if (pieces[0] == "merge-completed") {
status->state = SnapshotState::MergeCompleted;
} else {
LOG(ERROR) << "Unrecognized state " << pieces[0] << " for snapshot: " << name;
return false;
if (status->name() != name) {
LOG(WARNING) << "Found snapshot status named " << status->name() << " in " << path;
status->set_name(name);
}
if (!android::base::ParseUint(pieces[1], &status->device_size)) {
LOG(ERROR) << "Invalid device size in status line for: " << path;
return false;
}
if (!android::base::ParseUint(pieces[2], &status->snapshot_size)) {
LOG(ERROR) << "Invalid snapshot size in status line for: " << path;
return false;
}
if (!android::base::ParseUint(pieces[3], &status->cow_partition_size)) {
LOG(ERROR) << "Invalid cow linear size in status line for: " << path;
return false;
}
if (!android::base::ParseUint(pieces[4], &status->cow_file_size)) {
LOG(ERROR) << "Invalid cow file size in status line for: " << path;
return false;
}
if (!android::base::ParseUint(pieces[5], &status->sectors_allocated)) {
LOG(ERROR) << "Invalid snapshot size in status line for: " << path;
return false;
}
if (!android::base::ParseUint(pieces[6], &status->metadata_sectors)) {
LOG(ERROR) << "Invalid snapshot size in status line for: " << path;
return false;
}
return true;
}
std::string SnapshotManager::to_string(SnapshotState state) {
switch (state) {
case SnapshotState::None:
return "none";
case SnapshotState::Created:
return "created";
case SnapshotState::Merging:
return "merging";
case SnapshotState::MergeCompleted:
return "merge-completed";
default:
LOG(ERROR) << "Unknown snapshot state: " << (int)state;
return "unknown";
}
}
bool SnapshotManager::WriteSnapshotStatus(LockedFile* lock, const std::string& name,
const SnapshotStatus& status) {
bool SnapshotManager::WriteSnapshotStatus(LockedFile* lock, const SnapshotStatus& status) {
// The caller must take an exclusive lock to modify snapshots.
CHECK(lock);
CHECK(lock->lock_mode() == LOCK_EX);
CHECK(!status.name().empty());
auto path = GetSnapshotStatusFilePath(name);
unique_fd fd(open(path.c_str(), O_RDWR | O_CLOEXEC | O_NOFOLLOW | O_CREAT | O_SYNC, 0660));
auto path = GetSnapshotStatusFilePath(status.name());
unique_fd fd(
open(path.c_str(), O_RDWR | O_CLOEXEC | O_NOFOLLOW | O_CREAT | O_SYNC | O_TRUNC, 0660));
if (fd < 0) {
PLOG(ERROR) << "Open failed: " << path;
return false;
}
std::vector<std::string> pieces = {
to_string(status.state),
std::to_string(status.device_size),
std::to_string(status.snapshot_size),
std::to_string(status.cow_partition_size),
std::to_string(status.cow_file_size),
std::to_string(status.sectors_allocated),
std::to_string(status.metadata_sectors),
};
auto contents = android::base::Join(pieces, " ");
if (!android::base::WriteStringToFd(contents, fd)) {
PLOG(ERROR) << "write failed: " << path;
if (!status.SerializeToFileDescriptor(fd.get())) {
PLOG(ERROR) << "Unable to write SnapshotStatus to " << path;
return false;
}
return true;
}
@ -1714,7 +1660,7 @@ bool SnapshotManager::Truncate(LockedFile* file) {
std::string SnapshotManager::GetSnapshotDeviceName(const std::string& snapshot_name,
const SnapshotStatus& status) {
if (status.device_size != status.snapshot_size) {
if (status.device_size() != status.snapshot_size()) {
return GetSnapshotExtraDeviceName(snapshot_name);
}
return snapshot_name;
@ -1884,11 +1830,11 @@ bool SnapshotManager::CreateUpdateSnapshotsInternal(
}
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
<< ", 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;
<< 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, target_partition->name())) {
@ -1899,9 +1845,9 @@ bool SnapshotManager::CreateUpdateSnapshotsInternal(
// 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;
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) {
@ -1911,17 +1857,17 @@ bool SnapshotManager::CreateUpdateSnapshotsInternal(
}
// Store these device sizes to snapshot status file.
if (!CreateSnapshot(lock, target_partition->name(), cow_creator_ret->snapshot_status)) {
if (!CreateSnapshot(lock, &cow_creator_ret->snapshot_status)) {
return false;
}
created_devices->EmplaceBack<AutoDeleteSnapshot>(this, lock, 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)
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
<< 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 */);
@ -1930,10 +1876,10 @@ bool SnapshotManager::CreateUpdateSnapshotsInternal(
}
if (!target_metadata->ResizePartition(
cow_partition, cow_creator_ret->snapshot_status.cow_partition_size,
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;
<< 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
@ -1941,7 +1887,7 @@ bool SnapshotManager::CreateUpdateSnapshotsInternal(
}
// Create the backing COW image if necessary.
if (cow_creator_ret->snapshot_status.cow_file_size > 0) {
if (cow_creator_ret->snapshot_status.cow_file_size() > 0) {
if (!CreateCowImage(lock, target_partition->name())) {
return false;
}
@ -2049,13 +1995,13 @@ bool SnapshotManager::Dump(std::ostream& os) {
ok = false;
continue;
}
ss << " state: " << to_string(status.state) << std::endl;
ss << " device size (bytes): " << status.device_size << std::endl;
ss << " snapshot size (bytes): " << status.snapshot_size << std::endl;
ss << " cow partition size (bytes): " << status.cow_partition_size << std::endl;
ss << " cow file size (bytes): " << status.cow_file_size << std::endl;
ss << " allocated sectors: " << status.sectors_allocated << std::endl;
ss << " metadata sectors: " << status.metadata_sectors << std::endl;
ss << " state: " << SnapshotState_Name(status.state()) << std::endl;
ss << " device size (bytes): " << status.device_size() << std::endl;
ss << " snapshot size (bytes): " << status.snapshot_size() << std::endl;
ss << " cow partition size (bytes): " << status.cow_partition_size() << std::endl;
ss << " cow file size (bytes): " << status.cow_file_size() << std::endl;
ss << " allocated sectors: " << status.sectors_allocated() << std::endl;
ss << " metadata sectors: " << status.metadata_sectors() << std::endl;
}
os << ss.rdbuf();
return ok;

View File

@ -33,6 +33,7 @@
#include <liblp/builder.h>
#include <storage_literals/storage_literals.h>
#include <android/snapshot/snapshot.pb.h>
#include "test_helpers.h"
#include "utility.h"
@ -272,10 +273,12 @@ TEST_F(SnapshotTest, CreateSnapshot) {
ASSERT_TRUE(AcquireLock());
static const uint64_t kDeviceSize = 1024 * 1024;
ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), "test-snapshot",
{.device_size = kDeviceSize,
.snapshot_size = kDeviceSize,
.cow_file_size = kDeviceSize}));
SnapshotStatus status;
status.set_name("test-snapshot");
status.set_device_size(kDeviceSize);
status.set_snapshot_size(kDeviceSize);
status.set_cow_file_size(kDeviceSize);
ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), &status));
ASSERT_TRUE(CreateCowImage("test-snapshot"));
std::vector<std::string> snapshots;
@ -285,11 +288,11 @@ TEST_F(SnapshotTest, CreateSnapshot) {
// Scope so delete can re-acquire the snapshot file lock.
{
SnapshotManager::SnapshotStatus status;
SnapshotStatus status;
ASSERT_TRUE(sm->ReadSnapshotStatus(lock_.get(), "test-snapshot", &status));
ASSERT_EQ(status.state, SnapshotManager::SnapshotState::Created);
ASSERT_EQ(status.device_size, kDeviceSize);
ASSERT_EQ(status.snapshot_size, kDeviceSize);
ASSERT_EQ(status.state(), SnapshotState::CREATED);
ASSERT_EQ(status.device_size(), kDeviceSize);
ASSERT_EQ(status.snapshot_size(), kDeviceSize);
}
ASSERT_TRUE(sm->UnmapSnapshot(lock_.get(), "test-snapshot"));
@ -301,10 +304,12 @@ TEST_F(SnapshotTest, MapSnapshot) {
ASSERT_TRUE(AcquireLock());
static const uint64_t kDeviceSize = 1024 * 1024;
ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), "test-snapshot",
{.device_size = kDeviceSize,
.snapshot_size = kDeviceSize,
.cow_file_size = kDeviceSize}));
SnapshotStatus status;
status.set_name("test-snapshot");
status.set_device_size(kDeviceSize);
status.set_snapshot_size(kDeviceSize);
status.set_cow_file_size(kDeviceSize);
ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), &status));
ASSERT_TRUE(CreateCowImage("test-snapshot"));
std::string base_device;
@ -324,10 +329,12 @@ TEST_F(SnapshotTest, MapPartialSnapshot) {
static const uint64_t kSnapshotSize = 1024 * 1024;
static const uint64_t kDeviceSize = 1024 * 1024 * 2;
ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), "test-snapshot",
{.device_size = kDeviceSize,
.snapshot_size = kSnapshotSize,
.cow_file_size = kSnapshotSize}));
SnapshotStatus status;
status.set_name("test-snapshot");
status.set_device_size(kDeviceSize);
status.set_snapshot_size(kSnapshotSize);
status.set_cow_file_size(kSnapshotSize);
ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), &status));
ASSERT_TRUE(CreateCowImage("test-snapshot"));
std::string base_device;
@ -377,10 +384,12 @@ TEST_F(SnapshotTest, Merge) {
ASSERT_TRUE(CreatePartition("test_partition_a", kDeviceSize));
ASSERT_TRUE(MapUpdatePartitions());
ASSERT_TRUE(dm_.GetDmDevicePathByName("test_partition_b-base", &base_device));
ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), "test_partition_b",
{.device_size = kDeviceSize,
.snapshot_size = kDeviceSize,
.cow_file_size = kDeviceSize}));
SnapshotStatus status;
status.set_name("test_partition_b");
status.set_device_size(kDeviceSize);
status.set_snapshot_size(kDeviceSize);
status.set_cow_file_size(kDeviceSize);
ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), &status));
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,
@ -436,10 +445,12 @@ TEST_F(SnapshotTest, MergeCannotRemoveCow) {
ASSERT_TRUE(AcquireLock());
static const uint64_t kDeviceSize = 1024 * 1024;
ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), "test-snapshot",
{.device_size = kDeviceSize,
.snapshot_size = kDeviceSize,
.cow_file_size = kDeviceSize}));
SnapshotStatus status;
status.set_name("test-snapshot");
status.set_device_size(kDeviceSize);
status.set_snapshot_size(kDeviceSize);
status.set_cow_file_size(kDeviceSize);
ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), &status));
ASSERT_TRUE(CreateCowImage("test-snapshot"));
std::string base_device, cow_device, snap_device;
@ -492,10 +503,12 @@ TEST_F(SnapshotTest, FirstStageMountAndMerge) {
ASSERT_TRUE(CreatePartition("test_partition_a", kDeviceSize));
ASSERT_TRUE(MapUpdatePartitions());
ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), "test_partition_b",
{.device_size = kDeviceSize,
.snapshot_size = kDeviceSize,
.cow_file_size = kDeviceSize}));
SnapshotStatus status;
status.set_name("test_partition_b");
status.set_device_size(kDeviceSize);
status.set_snapshot_size(kDeviceSize);
status.set_cow_file_size(kDeviceSize);
ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), &status));
ASSERT_TRUE(CreateCowImage("test_partition_b"));
// Simulate a reboot into the new slot.
@ -511,9 +524,8 @@ TEST_F(SnapshotTest, FirstStageMountAndMerge) {
ASSERT_TRUE(AcquireLock());
// Validate that we have a snapshot device.
SnapshotManager::SnapshotStatus status;
ASSERT_TRUE(init->ReadSnapshotStatus(lock_.get(), "test_partition_b", &status));
ASSERT_EQ(status.state, SnapshotManager::SnapshotState::Created);
ASSERT_EQ(status.state(), SnapshotState::CREATED);
DeviceMapper::TargetInfo target;
auto dm_name = init->GetSnapshotDeviceName("test_partition_b", status);
@ -528,10 +540,12 @@ TEST_F(SnapshotTest, FlashSuperDuringUpdate) {
ASSERT_TRUE(CreatePartition("test_partition_a", kDeviceSize));
ASSERT_TRUE(MapUpdatePartitions());
ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), "test_partition_b",
{.device_size = kDeviceSize,
.snapshot_size = kDeviceSize,
.cow_file_size = kDeviceSize}));
SnapshotStatus status;
status.set_name("test_partition_b");
status.set_device_size(kDeviceSize);
status.set_snapshot_size(kDeviceSize);
status.set_cow_file_size(kDeviceSize);
ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), &status));
ASSERT_TRUE(CreateCowImage("test_partition_b"));
// Simulate a reboot into the new slot.
@ -550,7 +564,6 @@ TEST_F(SnapshotTest, FlashSuperDuringUpdate) {
ASSERT_TRUE(AcquireLock());
SnapshotManager::SnapshotStatus status;
ASSERT_TRUE(init->ReadSnapshotStatus(lock_.get(), "test_partition_b", &status));
// We should not get a snapshot device now.
@ -570,10 +583,12 @@ TEST_F(SnapshotTest, FlashSuperDuringMerge) {
ASSERT_TRUE(CreatePartition("test_partition_a", kDeviceSize));
ASSERT_TRUE(MapUpdatePartitions());
ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), "test_partition_b",
{.device_size = kDeviceSize,
.snapshot_size = kDeviceSize,
.cow_file_size = kDeviceSize}));
SnapshotStatus status;
status.set_name("test_partition_b");
status.set_device_size(kDeviceSize);
status.set_snapshot_size(kDeviceSize);
status.set_cow_file_size(kDeviceSize);
ASSERT_TRUE(sm->CreateSnapshot(lock_.get(), &status));
ASSERT_TRUE(CreateCowImage("test_partition_b"));
// Simulate a reboot into the new slot.
@ -699,11 +714,11 @@ class SnapshotUpdateTest : public SnapshotTest {
}
auto local_lock = std::move(lock_);
SnapshotManager::SnapshotStatus status;
SnapshotStatus status;
if (!sm->ReadSnapshotStatus(local_lock.get(), name, &status)) {
return std::nullopt;
}
return status.snapshot_size;
return status.snapshot_size();
}
AssertionResult UnmapAll() {
@ -869,8 +884,9 @@ TEST_F(SnapshotUpdateTest, SnapshotStatusFileWithoutCow) {
{
ASSERT_TRUE(AcquireLock());
auto local_lock = std::move(lock_);
ASSERT_TRUE(sm->WriteSnapshotStatus(local_lock.get(), "sys_b",
SnapshotManager::SnapshotStatus{}));
SnapshotStatus status;
status.set_name("sys_b");
ASSERT_TRUE(sm->WriteSnapshotStatus(local_lock.get(), status));
ASSERT_TRUE(image_manager_->CreateBackingImage("sys_b-cow-img", 1_MiB,
IImageManager::CREATE_IMAGE_DEFAULT));
}

View File

@ -113,6 +113,7 @@ LOCAL_STATIC_LIBRARIES := \
libbacktrace \
libmodprobe \
libext2_uuid \
libprotobuf-cpp-lite \
libsnapshot_nobinder \
LOCAL_SANITIZE := signed-integer-overflow