blob: 4743a42a4b230541a6609d386509bb870c73ad58 [file] [log] [blame]
// 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 <libsnapshot/snapshot.h>
#include <dirent.h>
#include <fcntl.h>
#include <math.h>
#include <sys/file.h>
#include <sys/types.h>
#include <sys/unistd.h>
#include <filesystem>
#include <optional>
#include <thread>
#include <unordered_set>
#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/parseint.h>
#include <android-base/properties.h>
#include <android-base/strings.h>
#include <android-base/unique_fd.h>
#include <cutils/sockets.h>
#include <ext4_utils/ext4_utils.h>
#include <fs_mgr.h>
#include <fs_mgr/file_wait.h>
#include <fs_mgr_dm_linear.h>
#include <fstab/fstab.h>
#include <libdm/dm.h>
#include <libfiemap/image_manager.h>
#include <liblp/liblp.h>
#include <android/snapshot/snapshot.pb.h>
#include <libsnapshot/snapshot_stats.h>
#include "device_info.h"
#include "libsnapshot_cow/parser_v2.h"
#include "partition_cow_creator.h"
#include "snapshot_metadata_updater.h"
#include "utility.h"
namespace android {
namespace snapshot {
using aidl::android::hardware::boot::MergeStatus;
using android::base::unique_fd;
using android::dm::DeviceMapper;
using android::dm::DmDeviceState;
using android::dm::DmTable;
using android::dm::DmTargetLinear;
using android::dm::DmTargetSnapshot;
using android::dm::DmTargetUser;
using android::dm::kSectorSize;
using android::dm::SnapshotStorageMode;
using android::fiemap::FiemapStatus;
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 chromeos_update_engine::DeltaArchiveManifest;
using chromeos_update_engine::Extent;
using chromeos_update_engine::FileDescriptor;
using chromeos_update_engine::PartitionUpdate;
template <typename T>
using RepeatedPtrField = google::protobuf::RepeatedPtrField<T>;
using std::chrono::duration_cast;
using namespace std::chrono_literals;
using namespace std::string_literals;
static constexpr char kBootSnapshotsWithoutSlotSwitch[] =
"/metadata/ota/snapshot-boot-without-slot-switch";
static constexpr char kBootIndicatorPath[] = "/metadata/ota/snapshot-boot";
static constexpr char kRollbackIndicatorPath[] = "/metadata/ota/rollback-indicator";
static constexpr auto kUpdateStateCheckInterval = 2s;
MergeFailureCode CheckMergeConsistency(const std::string& name, const SnapshotStatus& status);
// Note: IImageManager is an incomplete type in the header, so the default
// destructor doesn't work.
SnapshotManager::~SnapshotManager() {}
std::unique_ptr<SnapshotManager> SnapshotManager::New(IDeviceInfo* info) {
if (!info) {
info = new DeviceInfo();
}
return std::unique_ptr<SnapshotManager>(new SnapshotManager(info));
}
std::unique_ptr<SnapshotManager> SnapshotManager::NewForFirstStageMount(IDeviceInfo* info) {
if (!info) {
DeviceInfo* impl = new DeviceInfo();
impl->set_first_stage_init(true);
info = impl;
}
auto sm = New(info);
// The first-stage version of snapuserd is explicitly started by init. Do
// not attempt to using it during tests (which run in normal AOSP).
if (!sm->device()->IsTestDevice()) {
sm->use_first_stage_snapuserd_ = true;
}
return sm;
}
SnapshotManager::SnapshotManager(IDeviceInfo* device)
: dm_(device->GetDeviceMapper()), device_(device), metadata_dir_(device_->GetMetadataDir()) {
merge_consistency_checker_ = android::snapshot::CheckMergeConsistency;
}
static std::string GetCowName(const std::string& snapshot_name) {
return snapshot_name + "-cow";
}
SnapshotManager::SnapshotDriver SnapshotManager::GetSnapshotDriver(LockedFile* lock) {
if (UpdateUsesUserSnapshots(lock)) {
return SnapshotManager::SnapshotDriver::DM_USER;
} else {
return SnapshotManager::SnapshotDriver::DM_SNAPSHOT;
}
}
static std::string GetDmUserCowName(const std::string& snapshot_name,
SnapshotManager::SnapshotDriver driver) {
// dm-user block device will act as a snapshot device. We identify it with
// the same partition name so that when partitions can be mounted off
// dm-user.
switch (driver) {
case SnapshotManager::SnapshotDriver::DM_USER: {
return snapshot_name;
}
case SnapshotManager::SnapshotDriver::DM_SNAPSHOT: {
return snapshot_name + "-user-cow";
}
default: {
LOG(ERROR) << "Invalid snapshot driver";
return "";
}
}
}
static std::string GetCowImageDeviceName(const std::string& snapshot_name) {
return snapshot_name + "-cow-img";
}
static std::string GetBaseDeviceName(const std::string& partition_name) {
return partition_name + "-base";
}
static std::string GetSourceDeviceName(const std::string& partition_name) {
return partition_name + "-src";
}
bool SnapshotManager::BeginUpdate() {
bool needs_merge = false;
if (!TryCancelUpdate(&needs_merge)) {
return false;
}
if (needs_merge) {
LOG(INFO) << "Wait for merge (if any) before beginning a new update.";
auto state = ProcessUpdateState();
LOG(INFO) << "Merged with state = " << state;
}
auto file = LockExclusive();
if (!file) return false;
// Purge the ImageManager just in case there is a corrupt lp_metadata file
// lying around. (NB: no need to return false on an error, we can let the
// update try to progress.)
if (EnsureImageManager()) {
images_->RemoveAllImages();
}
// Clear any cached metadata (this allows re-using one manager across tests).
old_partition_metadata_ = nullptr;
auto state = ReadUpdateState(file.get());
if (state != UpdateState::None) {
LOG(ERROR) << "An update is already in progress, cannot begin a new update";
return false;
}
return WriteUpdateState(file.get(), UpdateState::Initiated);
}
bool SnapshotManager::CancelUpdate() {
bool needs_merge = false;
if (!TryCancelUpdate(&needs_merge)) {
return false;
}
if (needs_merge) {
LOG(ERROR) << "Cannot cancel update after it has completed or started merging";
}
return !needs_merge;
}
bool SnapshotManager::TryCancelUpdate(bool* needs_merge) {
*needs_merge = false;
auto file = LockExclusive();
if (!file) return false;
if (IsSnapshotWithoutSlotSwitch()) {
LOG(ERROR) << "Cannot cancel the snapshots as partitions are mounted off the snapshots on "
"current slot.";
return false;
}
UpdateState state = ReadUpdateState(file.get());
if (state == UpdateState::None) {
RemoveInvalidSnapshots(file.get());
return true;
}
if (state == UpdateState::Initiated) {
LOG(INFO) << "Update has been initiated, now canceling";
return RemoveAllUpdateState(file.get());
}
if (state == UpdateState::Unverified) {
// We completed an update, but it can still be canceled if we haven't booted into it.
auto slot = GetCurrentSlot();
if (slot != Slot::Target) {
LOG(INFO) << "Canceling previously completed updates (if any)";
return RemoveAllUpdateState(file.get());
}
}
*needs_merge = true;
return true;
}
std::string SnapshotManager::ReadUpdateSourceSlotSuffix() {
auto boot_file = GetSnapshotBootIndicatorPath();
std::string contents;
if (!android::base::ReadFileToString(boot_file, &contents)) {
PLOG(WARNING) << "Cannot read " << boot_file;
return {};
}
return contents;
}
SnapshotManager::Slot SnapshotManager::GetCurrentSlot() {
auto contents = ReadUpdateSourceSlotSuffix();
if (contents.empty()) {
return Slot::Unknown;
}
if (device_->GetSlotSuffix() == contents) {
return Slot::Source;
}
return Slot::Target;
}
std::string SnapshotManager::GetSnapshotSlotSuffix() {
switch (GetCurrentSlot()) {
case Slot::Target:
return device_->GetSlotSuffix();
default:
return device_->GetOtherSlotSuffix();
}
}
static bool RemoveFileIfExists(const std::string& path) {
std::string message;
if (!android::base::RemoveFileIfExists(path, &message)) {
LOG(ERROR) << "Remove failed: " << path << ": " << message;
return false;
}
return true;
}
bool SnapshotManager::RemoveAllUpdateState(LockedFile* lock, const std::function<bool()>& prolog) {
if (prolog && !prolog()) {
LOG(WARNING) << "Can't RemoveAllUpdateState: prolog failed.";
return false;
}
LOG(INFO) << "Removing all update state.";
if (!RemoveAllSnapshots(lock)) {
LOG(ERROR) << "Could not remove all snapshots";
return false;
}
// It's okay if these fail:
// - For SnapshotBoot and Rollback, first-stage init performs a deeper check after
// reading the indicator file, so it's not a problem if it still exists
// after the update completes.
// - For ForwardMerge, FinishedSnapshotWrites asserts that the existence of the indicator
// matches the incoming update.
std::vector<std::string> files = {
GetSnapshotBootIndicatorPath(), GetRollbackIndicatorPath(),
GetForwardMergeIndicatorPath(), GetOldPartitionMetadataPath(),
GetBootSnapshotsWithoutSlotSwitchPath(),
};
for (const auto& file : files) {
RemoveFileIfExists(file);
}
// If this fails, we'll keep trying to remove the update state (as the
// device reboots or starts a new update) until it finally succeeds.
return WriteUpdateState(lock, UpdateState::None);
}
bool SnapshotManager::FinishedSnapshotWrites(bool wipe) {
auto lock = LockExclusive();
if (!lock) return false;
auto update_state = ReadUpdateState(lock.get());
if (update_state == UpdateState::Unverified) {
LOG(INFO) << "FinishedSnapshotWrites already called before. Ignored.";
return true;
}
if (update_state != UpdateState::Initiated) {
LOG(ERROR) << "Can only transition to the Unverified state from the Initiated state.";
return false;
}
if (!EnsureNoOverflowSnapshot(lock.get())) {
LOG(ERROR) << "Cannot ensure there are no overflow snapshots.";
return false;
}
if (!UpdateForwardMergeIndicator(wipe)) {
return false;
}
// This file is written on boot to detect whether a rollback occurred. It
// MUST NOT exist before rebooting, otherwise, we're at risk of deleting
// snapshots too early.
if (!RemoveFileIfExists(GetRollbackIndicatorPath())) {
return false;
}
// This file acts as both a quick indicator for init (it can use access(2)
// to decide how to do first-stage mounts), and it stores the old slot, so
// we can tell whether or not we performed a rollback.
auto contents = device_->GetSlotSuffix();
auto boot_file = GetSnapshotBootIndicatorPath();
if (!WriteStringToFileAtomic(contents, boot_file)) {
PLOG(ERROR) << "write failed: " << boot_file;
return false;
}
return WriteUpdateState(lock.get(), UpdateState::Unverified);
}
bool SnapshotManager::CreateSnapshot(LockedFile* lock, PartitionCowCreator* cow_creator,
SnapshotStatus* status) {
CHECK(lock);
CHECK(lock->lock_mode() == LOCK_EX);
CHECK(status);
if (status->name().empty()) {
LOG(ERROR) << "SnapshotStatus has no name.";
return false;
}
// 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 " << 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 " << 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 " << status->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 " << status->name()
<< " cow file size is not a multiple of the sector size: "
<< status->cow_file_size();
return false;
}
status->set_state(SnapshotState::CREATED);
status->set_sectors_allocated(0);
status->set_metadata_sectors(0);
status->set_using_snapuserd(cow_creator->using_snapuserd);
status->set_compression_algorithm(cow_creator->compression_algorithm);
if (cow_creator->enable_threading) {
status->set_enable_threading(cow_creator->enable_threading);
}
if (cow_creator->batched_writes) {
status->set_batched_writes(cow_creator->batched_writes);
}
if (!WriteSnapshotStatus(lock, *status)) {
PLOG(ERROR) << "Could not write snapshot status: " << status->name();
return false;
}
return true;
}
Return SnapshotManager::CreateCowImage(LockedFile* lock, const std::string& name) {
CHECK(lock);
CHECK(lock->lock_mode() == LOCK_EX);
if (!EnsureImageManager()) return Return::Error();
SnapshotStatus status;
if (!ReadSnapshotStatus(lock, name, &status)) {
return Return::Error();
}
// The COW file size should have been rounded up to the nearest sector in CreateSnapshot.
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();
return Return::Error();
}
std::string cow_image_name = GetCowImageDeviceName(name);
int cow_flags = IImageManager::CREATE_IMAGE_DEFAULT;
return Return(images_->CreateBackingImage(cow_image_name, status.cow_file_size(), cow_flags));
}
bool SnapshotManager::MapDmUserCow(LockedFile* lock, const std::string& name,
const std::string& cow_file, const std::string& base_device,
const std::string& base_path_merge,
const std::chrono::milliseconds& timeout_ms, std::string* path) {
CHECK(lock);
if (UpdateUsesUserSnapshots(lock)) {
SnapshotStatus status;
if (!ReadSnapshotStatus(lock, name, &status)) {
LOG(ERROR) << "MapDmUserCow: ReadSnapshotStatus failed...";
return false;
}
if (status.state() == SnapshotState::NONE ||
status.state() == SnapshotState::MERGE_COMPLETED) {
LOG(ERROR) << "Should not create a snapshot device for " << name
<< " after merging has completed.";
return false;
}
SnapshotUpdateStatus update_status = ReadSnapshotUpdateStatus(lock);
if (update_status.state() == UpdateState::MergeCompleted ||
update_status.state() == UpdateState::MergeNeedsReboot) {
LOG(ERROR) << "Should not create a snapshot device for " << name
<< " after global merging has completed.";
return false;
}
}
// Use an extra decoration for first-stage init, so we can transition
// to a new table entry in second-stage.
std::string misc_name = name;
if (use_first_stage_snapuserd_) {
misc_name += "-init";
}
if (!EnsureSnapuserdConnected()) {
return false;
}
uint64_t base_sectors = 0;
if (!UpdateUsesUserSnapshots(lock)) {
base_sectors = snapuserd_client_->InitDmUserCow(misc_name, cow_file, base_device);
if (base_sectors == 0) {
LOG(ERROR) << "Failed to retrieve base_sectors from Snapuserd";
return false;
}
} else if (IsSnapshotWithoutSlotSwitch()) {
// When snapshots are on current slot, we determine the size
// of block device based on the number of COW operations. We cannot
// use base device as it will be from older image.
size_t num_ops = 0;
uint64_t dev_sz = 0;
unique_fd fd(open(cow_file.c_str(), O_RDONLY | O_CLOEXEC));
if (fd < 0) {
PLOG(ERROR) << "Failed to open " << cow_file;
return false;
}
CowReader reader;
if (!reader.Parse(std::move(fd))) {
LOG(ERROR) << "Failed to parse cow " << cow_file;
return false;
}
const auto& header = reader.GetHeader();
if (header.prefix.major_version > 2) {
LOG(ERROR) << "COW format not supported";
return false;
}
num_ops = reader.get_num_total_data_ops();
dev_sz = (num_ops * header.block_size);
base_sectors = dev_sz >> 9;
} else {
// For userspace snapshots, the size of the base device is taken as the
// size of the dm-user block device. Since there is no pseudo mapping
// created in the daemon, we no longer need to rely on the daemon for
// sizing the dm-user block device.
unique_fd fd(TEMP_FAILURE_RETRY(open(base_path_merge.c_str(), O_RDONLY | O_CLOEXEC)));
if (fd < 0) {
LOG(ERROR) << "Cannot open block device: " << base_path_merge;
return false;
}
uint64_t dev_sz = get_block_device_size(fd.get());
if (!dev_sz) {
LOG(ERROR) << "Failed to find block device size: " << base_path_merge;
return false;
}
base_sectors = dev_sz >> 9;
}
DmTable table;
table.Emplace<DmTargetUser>(0, base_sectors, misc_name);
if (!dm_.CreateDevice(name, table, path, timeout_ms)) {
LOG(ERROR) << " dm-user: CreateDevice failed... ";
return false;
}
if (!WaitForDevice(*path, timeout_ms)) {
LOG(ERROR) << " dm-user: timeout: Failed to create block device for: " << name;
return false;
}
auto control_device = "/dev/dm-user/" + misc_name;
if (!WaitForDevice(control_device, timeout_ms)) {
return false;
}
if (UpdateUsesUserSnapshots(lock)) {
// Now that the dm-user device is created, initialize the daemon and
// spin up the worker threads.
if (!snapuserd_client_->InitDmUserCow(misc_name, cow_file, base_device, base_path_merge)) {
LOG(ERROR) << "InitDmUserCow failed";
return false;
}
}
return snapuserd_client_->AttachDmUser(misc_name);
}
bool SnapshotManager::MapSnapshot(LockedFile* lock, const std::string& name,
const std::string& base_device, const std::string& cow_device,
const std::chrono::milliseconds& timeout_ms,
std::string* dev_path) {
CHECK(lock);
SnapshotStatus status;
if (!ReadSnapshotStatus(lock, name, &status)) {
return false;
}
if (status.state() == SnapshotState::NONE || status.state() == SnapshotState::MERGE_COMPLETED) {
LOG(ERROR) << "Should not create a snapshot device for " << name
<< " after merging has completed.";
return false;
}
// Validate the block device size, as well as the requested snapshot size.
// Note that during first-stage init, we don't have the device paths.
if (android::base::StartsWith(base_device, "/")) {
unique_fd fd(open(base_device.c_str(), O_RDONLY | O_CLOEXEC));
if (fd < 0) {
PLOG(ERROR) << "open failed: " << base_device;
return false;
}
auto dev_size = get_block_device_size(fd);
if (!dev_size) {
PLOG(ERROR) << "Could not determine block device size: " << base_device;
return false;
}
if (status.device_size() != dev_size) {
LOG(ERROR) << "Block device size for " << base_device << " does not match"
<< "(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();
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();
return false;
}
if (status.device_size() != status.snapshot_size()) {
LOG(ERROR) << "Device size and snapshot size must be the same (device size = "
<< status.device_size() << ", snapshot size = " << status.snapshot_size();
return false;
}
uint64_t snapshot_sectors = status.snapshot_size() / kSectorSize;
// Note that merging is a global state. We do track whether individual devices
// have completed merging, but the start of the merge process is considered
// atomic.
SnapshotStorageMode mode;
SnapshotUpdateStatus update_status = ReadSnapshotUpdateStatus(lock);
switch (update_status.state()) {
case UpdateState::MergeCompleted:
case UpdateState::MergeNeedsReboot:
LOG(ERROR) << "Should not create a snapshot device for " << name
<< " after global merging has completed.";
return false;
case UpdateState::Merging:
case UpdateState::MergeFailed:
// Note: MergeFailed indicates that a merge is in progress, but
// is possibly stalled. We still have to honor the merge.
if (DecideMergePhase(status) == update_status.merge_phase()) {
mode = SnapshotStorageMode::Merge;
} else {
mode = SnapshotStorageMode::Persistent;
}
break;
default:
mode = SnapshotStorageMode::Persistent;
break;
}
if (mode == SnapshotStorageMode::Persistent && status.state() == SnapshotState::MERGING) {
LOG(ERROR) << "Snapshot: " << name
<< " has snapshot status Merging but mode set to Persistent."
<< " Changing mode to Snapshot-Merge.";
mode = SnapshotStorageMode::Merge;
}
DmTable table;
table.Emplace<DmTargetSnapshot>(0, snapshot_sectors, base_device, cow_device, mode,
kSnapshotChunkSize);
if (!dm_.CreateDevice(name, table, dev_path, timeout_ms)) {
LOG(ERROR) << "Could not create snapshot device: " << name;
return false;
}
return true;
}
std::optional<std::string> SnapshotManager::MapCowImage(
const std::string& name, const std::chrono::milliseconds& timeout_ms) {
if (!EnsureImageManager()) return std::nullopt;
auto cow_image_name = GetCowImageDeviceName(name);
bool ok;
std::string cow_dev;
if (device_->IsRecovery() || device_->IsFirstStageInit()) {
const auto& opener = device_->GetPartitionOpener();
ok = images_->MapImageWithDeviceMapper(opener, cow_image_name, &cow_dev);
} else {
ok = images_->MapImageDevice(cow_image_name, timeout_ms, &cow_dev);
}
if (ok) {
LOG(INFO) << "Mapped " << cow_image_name << " to " << cow_dev;
return cow_dev;
}
LOG(ERROR) << "Could not map image device: " << cow_image_name;
return std::nullopt;
}
bool SnapshotManager::MapSourceDevice(LockedFile* lock, const std::string& name,
const std::chrono::milliseconds& timeout_ms,
std::string* path) {
CHECK(lock);
auto metadata = ReadOldPartitionMetadata(lock);
if (!metadata) {
LOG(ERROR) << "Could not map source device due to missing or corrupt metadata";
return false;
}
auto old_name = GetOtherPartitionName(name);
auto slot_suffix = device_->GetSlotSuffix();
auto slot = SlotNumberForSlotSuffix(slot_suffix);
CreateLogicalPartitionParams params = {
.block_device = device_->GetSuperDevice(slot),
.metadata = metadata,
.partition_name = old_name,
.timeout_ms = timeout_ms,
.device_name = GetSourceDeviceName(name),
.partition_opener = &device_->GetPartitionOpener(),
};
if (!CreateLogicalPartition(std::move(params), path)) {
LOG(ERROR) << "Could not create source device for snapshot " << name;
return false;
}
return true;
}
bool SnapshotManager::UnmapSnapshot(LockedFile* lock, const std::string& name) {
CHECK(lock);
if (UpdateUsesUserSnapshots(lock)) {
if (!UnmapUserspaceSnapshotDevice(lock, name)) {
return false;
}
} else {
if (!DeleteDeviceIfExists(name)) {
LOG(ERROR) << "Could not delete snapshot device: " << name;
return false;
}
}
return true;
}
bool SnapshotManager::UnmapCowImage(const std::string& name) {
if (!EnsureImageManager()) return false;
return images_->UnmapImageIfExists(GetCowImageDeviceName(name));
}
bool SnapshotManager::DeleteSnapshot(LockedFile* lock, const std::string& name) {
CHECK(lock);
CHECK(lock->lock_mode() == LOCK_EX);
if (!EnsureImageManager()) return false;
if (!UnmapCowDevices(lock, name)) {
return false;
}
// We can't delete snapshots in recovery. The only way we'd try is it we're
// completing or canceling a merge in preparation for a data wipe, in which
// case, we don't care if the file sticks around.
if (device_->IsRecovery()) {
LOG(INFO) << "Skipping delete of snapshot " << name << " in recovery.";
return true;
}
auto cow_image_name = GetCowImageDeviceName(name);
if (images_->BackingImageExists(cow_image_name)) {
if (!images_->DeleteBackingImage(cow_image_name)) {
return false;
}
}
std::string error;
auto file_path = GetSnapshotStatusFilePath(name);
if (!android::base::RemoveFileIfExists(file_path, &error)) {
LOG(ERROR) << "Failed to remove status file " << file_path << ": " << error;
return false;
}
// This path may never exist. If it is present, then it's a stale
// snapshot status file. Just remove the file and log the message.
const std::string tmp_path = file_path + ".tmp";
if (!android::base::RemoveFileIfExists(tmp_path, &error)) {
LOG(ERROR) << "Failed to remove stale snapshot file " << tmp_path;
}
return true;
}
bool SnapshotManager::InitiateMerge() {
auto lock = LockExclusive();
if (!lock) return false;
UpdateState state = ReadUpdateState(lock.get());
if (state != UpdateState::Unverified) {
LOG(ERROR) << "Cannot begin a merge if an update has not been verified";
return false;
}
auto slot = GetCurrentSlot();
if (slot != Slot::Target) {
LOG(ERROR) << "Device cannot merge while not booting from new slot";
return false;
}
std::vector<std::string> snapshots;
if (!ListSnapshots(lock.get(), &snapshots)) {
LOG(ERROR) << "Could not list snapshots";
return false;
}
auto current_slot_suffix = device_->GetSlotSuffix();
for (const auto& snapshot : snapshots) {
if (!android::base::EndsWith(snapshot, current_slot_suffix)) {
// Allow the merge to continue, but log this unexpected case.
LOG(ERROR) << "Unexpected snapshot found during merge: " << snapshot;
continue;
}
// The device has to be mapped, since everything should be merged at
// the same time. This is a fairly serious error. We could forcefully
// map everything here, but it should have been mapped during first-
// stage init.
if (dm_.GetState(snapshot) == DmDeviceState::INVALID) {
LOG(ERROR) << "Cannot begin merge; device " << snapshot << " is not mapped.";
return false;
}
}
auto metadata = ReadCurrentMetadata();
for (auto it = snapshots.begin(); it != snapshots.end();) {
switch (GetMetadataPartitionState(*metadata, *it)) {
case MetadataPartitionState::Flashed:
LOG(WARNING) << "Detected re-flashing for partition " << *it
<< ". Skip merging it.";
[[fallthrough]];
case MetadataPartitionState::None: {
LOG(WARNING) << "Deleting snapshot for partition " << *it;
if (!DeleteSnapshot(lock.get(), *it)) {
LOG(WARNING) << "Cannot delete snapshot for partition " << *it
<< ". Skip merging it anyways.";
}
it = snapshots.erase(it);
} break;
case MetadataPartitionState::Updated: {
++it;
} break;
}
}
bool using_snapuserd = false;
std::vector<std::string> first_merge_group;
DmTargetSnapshot::Status initial_target_values = {};
for (const auto& snapshot : snapshots) {
if (!UpdateUsesUserSnapshots(lock.get())) {
DmTargetSnapshot::Status current_status;
if (!QuerySnapshotStatus(snapshot, nullptr, &current_status)) {
return false;
}
initial_target_values.sectors_allocated += current_status.sectors_allocated;
initial_target_values.total_sectors += current_status.total_sectors;
initial_target_values.metadata_sectors += current_status.metadata_sectors;
}
SnapshotStatus snapshot_status;
if (!ReadSnapshotStatus(lock.get(), snapshot, &snapshot_status)) {
return false;
}
using_snapuserd |= snapshot_status.using_snapuserd();
if (DecideMergePhase(snapshot_status) == MergePhase::FIRST_PHASE) {
first_merge_group.emplace_back(snapshot);
}
}
SnapshotUpdateStatus initial_status = ReadSnapshotUpdateStatus(lock.get());
initial_status.set_state(UpdateState::Merging);
initial_status.set_using_snapuserd(using_snapuserd);
if (!UpdateUsesUserSnapshots(lock.get())) {
initial_status.set_sectors_allocated(initial_target_values.sectors_allocated);
initial_status.set_total_sectors(initial_target_values.total_sectors);
initial_status.set_metadata_sectors(initial_target_values.metadata_sectors);
}
// If any partitions shrunk, we need to merge them before we merge any other
// partitions (see b/177935716). Otherwise, a merge from another partition
// may overwrite the source block of a copy operation.
const std::vector<std::string>* merge_group;
if (first_merge_group.empty()) {
merge_group = &snapshots;
initial_status.set_merge_phase(MergePhase::SECOND_PHASE);
} else {
merge_group = &first_merge_group;
initial_status.set_merge_phase(MergePhase::FIRST_PHASE);
}
// Point of no return - mark that we're starting a merge. From now on every
// eligible snapshot must be a merge target.
if (!WriteSnapshotUpdateStatus(lock.get(), initial_status)) {
return false;
}
auto reported_code = MergeFailureCode::Ok;
for (const auto& snapshot : *merge_group) {
// If this fails, we have no choice but to continue. Everything must
// be merged. This is not an ideal state to be in, but it is safe,
// because we the next boot will try again.
auto code = SwitchSnapshotToMerge(lock.get(), snapshot);
if (code != MergeFailureCode::Ok) {
LOG(ERROR) << "Failed to switch snapshot to a merge target: " << snapshot;
if (reported_code == MergeFailureCode::Ok) {
reported_code = code;
}
}
}
// If we couldn't switch everything to a merge target, pre-emptively mark
// this merge as failed. It will get acknowledged when WaitForMerge() is
// called.
if (reported_code != MergeFailureCode::Ok) {
WriteUpdateState(lock.get(), UpdateState::MergeFailed, reported_code);
}
// Return true no matter what, because a merge was initiated.
return true;
}
MergeFailureCode SnapshotManager::SwitchSnapshotToMerge(LockedFile* lock, const std::string& name) {
SnapshotStatus status;
if (!ReadSnapshotStatus(lock, name, &status)) {
return MergeFailureCode::ReadStatus;
}
if (status.state() != SnapshotState::CREATED) {
LOG(WARNING) << "Snapshot " << name
<< " has unexpected state: " << SnapshotState_Name(status.state());
}
if (UpdateUsesUserSnapshots(lock)) {
if (EnsureSnapuserdConnected()) {
// This is the point where we inform the daemon to initiate/resume
// the merge
if (!snapuserd_client_->InitiateMerge(name)) {
return MergeFailureCode::UnknownTable;
}
} else {
LOG(ERROR) << "Failed to connect to snapuserd daemon to initiate merge";
return MergeFailureCode::UnknownTable;
}
} else {
// After this, we return true because we technically did switch to a merge
// target. Everything else we do here is just informational.
if (auto code = RewriteSnapshotDeviceTable(name); code != MergeFailureCode::Ok) {
return code;
}
}
status.set_state(SnapshotState::MERGING);
if (!UpdateUsesUserSnapshots(lock)) {
DmTargetSnapshot::Status dm_status;
if (!QuerySnapshotStatus(name, nullptr, &dm_status)) {
LOG(ERROR) << "Could not query merge status for snapshot: " << name;
}
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 MergeFailureCode::Ok;
}
MergeFailureCode SnapshotManager::RewriteSnapshotDeviceTable(const std::string& name) {
std::vector<DeviceMapper::TargetInfo> old_targets;
if (!dm_.GetTableInfo(name, &old_targets)) {
LOG(ERROR) << "Could not read snapshot device table: " << name;
return MergeFailureCode::GetTableInfo;
}
if (old_targets.size() != 1 || DeviceMapper::GetTargetType(old_targets[0].spec) != "snapshot") {
LOG(ERROR) << "Unexpected device-mapper table for snapshot: " << name;
return MergeFailureCode::UnknownTable;
}
std::string base_device, cow_device;
if (!DmTargetSnapshot::GetDevicesFromParams(old_targets[0].data, &base_device, &cow_device)) {
LOG(ERROR) << "Could not derive underlying devices for snapshot: " << name;
return MergeFailureCode::GetTableParams;
}
DmTable table;
table.Emplace<DmTargetSnapshot>(0, old_targets[0].spec.length, base_device, cow_device,
SnapshotStorageMode::Merge, kSnapshotChunkSize);
if (!dm_.LoadTableAndActivate(name, table)) {
LOG(ERROR) << "Could not swap device-mapper tables on snapshot device " << name;
return MergeFailureCode::ActivateNewTable;
}
LOG(INFO) << "Successfully switched snapshot device to a merge target: " << name;
return MergeFailureCode::Ok;
}
bool SnapshotManager::GetSingleTarget(const std::string& dm_name, TableQuery query,
DeviceMapper::TargetInfo* target) {
if (dm_.GetState(dm_name) == DmDeviceState::INVALID) {
return false;
}
std::vector<DeviceMapper::TargetInfo> targets;
bool result;
if (query == TableQuery::Status) {
result = dm_.GetTableStatus(dm_name, &targets);
} else {
result = dm_.GetTableInfo(dm_name, &targets);
}
if (!result) {
LOG(ERROR) << "Could not query device: " << dm_name;
return false;
}
if (targets.size() != 1) {
return false;
}
*target = std::move(targets[0]);
return true;
}
bool SnapshotManager::IsSnapshotDevice(const std::string& dm_name, TargetInfo* target) {
DeviceMapper::TargetInfo snap_target;
if (!GetSingleTarget(dm_name, TableQuery::Status, &snap_target)) {
return false;
}
auto type = DeviceMapper::GetTargetType(snap_target.spec);
// If this is not a user-snapshot device then it should either
// be a dm-snapshot or dm-snapshot-merge target
if (type != "user") {
if (type != "snapshot" && type != "snapshot-merge") {
return false;
}
}
if (target) {
*target = std::move(snap_target);
}
return true;
}
auto SnapshotManager::UpdateStateToStr(const enum UpdateState state) {
switch (state) {
case None:
return "None";
case Initiated:
return "Initiated";
case Unverified:
return "Unverified";
case Merging:
return "Merging";
case MergeNeedsReboot:
return "MergeNeedsReboot";
case MergeCompleted:
return "MergeCompleted";
case MergeFailed:
return "MergeFailed";
case Cancelled:
return "Cancelled";
default:
return "Unknown";
}
}
bool SnapshotManager::QuerySnapshotStatus(const std::string& dm_name, std::string* target_type,
DmTargetSnapshot::Status* status) {
DeviceMapper::TargetInfo target;
if (!IsSnapshotDevice(dm_name, &target)) {
LOG(ERROR) << "Device " << dm_name << " is not a snapshot or snapshot-merge device";
return false;
}
if (!DmTargetSnapshot::ParseStatusText(target.data, status)) {
LOG(ERROR) << "Could not parse snapshot status text: " << dm_name;
return false;
}
if (target_type) {
*target_type = DeviceMapper::GetTargetType(target.spec);
}
if (!status->error.empty()) {
LOG(ERROR) << "Snapshot: " << dm_name << " returned error code: " << status->error;
return false;
}
return true;
}
// Note that when a merge fails, we will *always* try again to complete the
// merge each time the device boots. There is no harm in doing so, and if
// the problem was transient, we might manage to get a new outcome.
UpdateState SnapshotManager::ProcessUpdateState(const std::function<bool()>& callback,
const std::function<bool()>& before_cancel) {
while (true) {
auto result = CheckMergeState(before_cancel);
LOG(INFO) << "ProcessUpdateState handling state: " << UpdateStateToStr(result.state);
if (result.state == UpdateState::MergeFailed) {
AcknowledgeMergeFailure(result.failure_code);
}
if (result.state != UpdateState::Merging) {
// Either there is no merge, or the merge was finished, so no need
// to keep waiting.
return result.state;
}
if (callback && !callback()) {
return result.state;
}
// This wait is not super time sensitive, so we have a relatively
// low polling frequency.
std::this_thread::sleep_for(kUpdateStateCheckInterval);
}
}
auto SnapshotManager::CheckMergeState(const std::function<bool()>& before_cancel) -> MergeResult {
auto lock = LockExclusive();
if (!lock) {
return MergeResult(UpdateState::MergeFailed, MergeFailureCode::AcquireLock);
}
auto result = CheckMergeState(lock.get(), before_cancel);
LOG(INFO) << "CheckMergeState for snapshots returned: " << UpdateStateToStr(result.state);
if (result.state == UpdateState::MergeCompleted) {
// Do this inside the same lock. Failures get acknowledged without the
// lock, because flock() might have failed.
AcknowledgeMergeSuccess(lock.get());
} else if (result.state == UpdateState::Cancelled) {
if (!device_->IsRecovery() && !RemoveAllUpdateState(lock.get(), before_cancel)) {
LOG(ERROR) << "Failed to remove all update state after acknowleding cancelled update.";
}
}
return result;
}
auto SnapshotManager::CheckMergeState(LockedFile* lock, const std::function<bool()>& before_cancel)
-> MergeResult {
SnapshotUpdateStatus update_status = ReadSnapshotUpdateStatus(lock);
switch (update_status.state()) {
case UpdateState::None:
case UpdateState::MergeCompleted:
// Harmless races are allowed between two callers of WaitForMerge,
// so in both of these cases we just propagate the state.
return MergeResult(update_status.state());
case UpdateState::Merging:
case UpdateState::MergeNeedsReboot:
case UpdateState::MergeFailed:
// We'll poll each snapshot below. Note that for the NeedsReboot
// case, we always poll once to give cleanup another opportunity to
// run.
break;
case UpdateState::Unverified:
// This is an edge case. Normally cancelled updates are detected
// via the merge poll below, but if we never started a merge, we
// need to also check here.
if (HandleCancelledUpdate(lock, before_cancel)) {
return MergeResult(UpdateState::Cancelled);
}
return MergeResult(update_status.state());
default:
return MergeResult(update_status.state());
}
std::vector<std::string> snapshots;
if (!ListSnapshots(lock, &snapshots)) {
return MergeResult(UpdateState::MergeFailed, MergeFailureCode::ListSnapshots);
}
auto current_slot_suffix = device_->GetSlotSuffix();
bool cancelled = false;
bool merging = false;
bool needs_reboot = false;
bool wrong_phase = false;
MergeFailureCode failure_code = MergeFailureCode::Ok;
for (const auto& snapshot : snapshots) {
if (!android::base::EndsWith(snapshot, current_slot_suffix)) {
// This will have triggered an error message in InitiateMerge already.
LOG(ERROR) << "Skipping merge validation of unexpected snapshot: " << snapshot;
continue;
}
auto result = CheckTargetMergeState(lock, snapshot, update_status);
LOG(INFO) << "CheckTargetMergeState for " << snapshot
<< " returned: " << UpdateStateToStr(result.state);
switch (result.state) {
case UpdateState::MergeFailed:
// Take the first failure code in case other failures compound.
if (failure_code == MergeFailureCode::Ok) {
failure_code = result.failure_code;
}
break;
case UpdateState::Merging:
merging = true;
break;
case UpdateState::MergeNeedsReboot:
needs_reboot = true;
break;
case UpdateState::MergeCompleted:
break;
case UpdateState::Cancelled:
cancelled = true;
break;
case UpdateState::None:
wrong_phase = true;
break;
default:
LOG(ERROR) << "Unknown merge status for \"" << snapshot << "\": "
<< "\"" << result.state << "\"";
if (failure_code == MergeFailureCode::Ok) {
failure_code = MergeFailureCode::UnexpectedMergeState;
}
break;
}
}
if (merging) {
// Note that we handle "Merging" before we handle anything else. We
// want to poll until *nothing* is merging if we can, so everything has
// a chance to get marked as completed or failed.
return MergeResult(UpdateState::Merging);
}
if (failure_code != MergeFailureCode::Ok) {
// Note: since there are many drop-out cases for failure, we acknowledge
// it in WaitForMerge rather than here and elsewhere.
return MergeResult(UpdateState::MergeFailed, failure_code);
}
if (wrong_phase) {
// If we got here, no other partitions are being merged, and nothing
// failed to merge. It's safe to move to the next merge phase.
auto code = MergeSecondPhaseSnapshots(lock);
if (code != MergeFailureCode::Ok) {
return MergeResult(UpdateState::MergeFailed, code);
}
return MergeResult(UpdateState::Merging);
}
if (needs_reboot) {
WriteUpdateState(lock, UpdateState::MergeNeedsReboot);
return MergeResult(UpdateState::MergeNeedsReboot);
}
if (cancelled) {
// This is an edge case, that we handle as correctly as we sensibly can.
// The underlying partition has changed behind update_engine, and we've
// removed the snapshot as a result. The exact state of the update is
// undefined now, but this can only happen on an unlocked device where
// partitions can be flashed without wiping userdata.
return MergeResult(UpdateState::Cancelled);
}
return MergeResult(UpdateState::MergeCompleted);
}
auto SnapshotManager::CheckTargetMergeState(LockedFile* lock, const std::string& name,
const SnapshotUpdateStatus& update_status)
-> MergeResult {
SnapshotStatus snapshot_status;
if (!ReadSnapshotStatus(lock, name, &snapshot_status)) {
return MergeResult(UpdateState::MergeFailed, MergeFailureCode::ReadStatus);
}
std::unique_ptr<LpMetadata> current_metadata;
if (!IsSnapshotDevice(name)) {
if (!current_metadata) {
current_metadata = ReadCurrentMetadata();
}
if (!current_metadata ||
GetMetadataPartitionState(*current_metadata, name) != MetadataPartitionState::Updated) {
DeleteSnapshot(lock, name);
return MergeResult(UpdateState::Cancelled);
}
// During a check, we decided the merge was complete, but we were unable to
// collapse the device-mapper stack and perform COW cleanup. If we haven't
// rebooted after this check, the device will still be a snapshot-merge
// target. If we have rebooted, the device will now be a linear target,
// and we can try cleanup again.
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 MergeResult(UpdateState::MergeCompleted);
}
LOG(ERROR) << "Expected snapshot or snapshot-merge for device: " << name;
return MergeResult(UpdateState::MergeFailed, MergeFailureCode::UnknownTargetType);
}
// This check is expensive so it is only enabled for debugging.
DCHECK((current_metadata = ReadCurrentMetadata()) &&
GetMetadataPartitionState(*current_metadata, name) == MetadataPartitionState::Updated);
if (UpdateUsesUserSnapshots(lock)) {
if (!EnsureSnapuserdConnected()) {
return MergeResult(UpdateState::MergeFailed, MergeFailureCode::QuerySnapshotStatus);
}
// Query the snapshot status from the daemon
const auto merge_status = snapuserd_client_->QuerySnapshotStatus(name);
if (merge_status == "snapshot-merge-failed") {
return MergeResult(UpdateState::MergeFailed, MergeFailureCode::UnknownTargetType);
}
// This is the case when device reboots during merge. Once the device boots,
// snapuserd daemon will not resume merge immediately in first stage init.
// This is slightly different as compared to dm-snapshot-merge; In this
// case, metadata file will have "MERGING" state whereas the daemon will be
// waiting to resume the merge. Thus, we resume the merge at this point.
if (merge_status == "snapshot" && snapshot_status.state() == SnapshotState::MERGING) {
if (!snapuserd_client_->InitiateMerge(name)) {
return MergeResult(UpdateState::MergeFailed, MergeFailureCode::UnknownTargetType);
}
return MergeResult(UpdateState::Merging);
}
if (merge_status == "snapshot" &&
DecideMergePhase(snapshot_status) == MergePhase::SECOND_PHASE &&
update_status.merge_phase() == MergePhase::FIRST_PHASE) {
// The snapshot is not being merged because it's in the wrong phase.
return MergeResult(UpdateState::None);
}
if (merge_status == "snapshot-merge") {
if (snapshot_status.state() == SnapshotState::MERGE_COMPLETED) {
LOG(ERROR) << "Snapshot " << name
<< " is merging after being marked merge-complete.";
return MergeResult(UpdateState::MergeFailed,
MergeFailureCode::UnmergedSectorsAfterCompletion);
}
return MergeResult(UpdateState::Merging);
}
if (merge_status != "snapshot-merge-complete") {
LOG(ERROR) << "Snapshot " << name << " has incorrect status: " << merge_status;
return MergeResult(UpdateState::MergeFailed, MergeFailureCode::ExpectedMergeTarget);
}
} else {
// dm-snapshot in the kernel
std::string target_type;
DmTargetSnapshot::Status status;
if (!QuerySnapshotStatus(name, &target_type, &status)) {
return MergeResult(UpdateState::MergeFailed, MergeFailureCode::QuerySnapshotStatus);
}
if (target_type == "snapshot" &&
DecideMergePhase(snapshot_status) == MergePhase::SECOND_PHASE &&
update_status.merge_phase() == MergePhase::FIRST_PHASE) {
// The snapshot is not being merged because it's in the wrong phase.
return MergeResult(UpdateState::None);
}
if (target_type != "snapshot-merge") {
// We can get here if we failed to rewrite the target type in
// InitiateMerge(). If we failed to create the target in first-stage
// init, boot would not succeed.
LOG(ERROR) << "Snapshot " << name << " has incorrect target type: " << target_type;
return MergeResult(UpdateState::MergeFailed, MergeFailureCode::ExpectedMergeTarget);
}
// These two values are equal when merging is complete.
if (status.sectors_allocated != status.metadata_sectors) {
if (snapshot_status.state() == SnapshotState::MERGE_COMPLETED) {
LOG(ERROR) << "Snapshot " << name
<< " is merging after being marked merge-complete.";
return MergeResult(UpdateState::MergeFailed,
MergeFailureCode::UnmergedSectorsAfterCompletion);
}
return MergeResult(UpdateState::Merging);
}
}
// Merge is complete at this point
auto code = CheckMergeConsistency(lock, name, snapshot_status);
if (code != MergeFailureCode::Ok) {
return MergeResult(UpdateState::MergeFailed, code);
}
// Merging is done. First, update the status file to indicate the merge
// is complete. We do this before calling OnSnapshotMergeComplete, even
// though this means the write is potentially wasted work (since in the
// ideal case we'll immediately delete the file).
//
// 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.set_state(SnapshotState::MERGE_COMPLETED);
if (!WriteSnapshotStatus(lock, snapshot_status)) {
return MergeResult(UpdateState::MergeFailed, MergeFailureCode::WriteStatus);
}
if (!OnSnapshotMergeComplete(lock, name, snapshot_status)) {
return MergeResult(UpdateState::MergeNeedsReboot);
}
return MergeResult(UpdateState::MergeCompleted, MergeFailureCode::Ok);
}
// This returns the backing device, not the dm-user layer.
static std::string GetMappedCowDeviceName(const std::string& snapshot,
const SnapshotStatus& status) {
// If no partition was created (the COW exists entirely on /data), the
// device-mapper layering is different than if we had a partition.
if (status.cow_partition_size() == 0) {
return GetCowImageDeviceName(snapshot);
}
return GetCowName(snapshot);
}
MergeFailureCode SnapshotManager::CheckMergeConsistency(LockedFile* lock, const std::string& name,
const SnapshotStatus& status) {
CHECK(lock);
return merge_consistency_checker_(name, status);
}
MergeFailureCode CheckMergeConsistency(const std::string& name, const SnapshotStatus& status) {
if (!status.using_snapuserd()) {
// Do not try to verify old-style COWs yet.
return MergeFailureCode::Ok;
}
auto& dm = DeviceMapper::Instance();
std::string cow_image_name = GetMappedCowDeviceName(name, status);
std::string cow_image_path;
if (!dm.GetDmDevicePathByName(cow_image_name, &cow_image_path)) {
LOG(ERROR) << "Failed to get path for cow device: " << cow_image_name;
return MergeFailureCode::GetCowPathConsistencyCheck;
}
// First pass, count # of ops.
size_t num_ops = 0;
{
unique_fd fd(open(cow_image_path.c_str(), O_RDONLY | O_CLOEXEC));
if (fd < 0) {
PLOG(ERROR) << "Failed to open " << cow_image_name;
return MergeFailureCode::OpenCowConsistencyCheck;
}
CowReader reader;
if (!reader.Parse(std::move(fd))) {
LOG(ERROR) << "Failed to parse cow " << cow_image_path;
return MergeFailureCode::ParseCowConsistencyCheck;
}
num_ops = reader.get_num_total_data_ops();
}
// Second pass, try as hard as we can to get the actual number of blocks
// the system thinks is merged.
unique_fd fd(open(cow_image_path.c_str(), O_RDONLY | O_DIRECT | O_SYNC | O_CLOEXEC));
if (fd < 0) {
PLOG(ERROR) << "Failed to open direct " << cow_image_name;
return MergeFailureCode::OpenCowDirectConsistencyCheck;
}
void* addr;
size_t page_size = getpagesize();
if (posix_memalign(&addr, page_size, page_size) < 0) {
PLOG(ERROR) << "posix_memalign with page size " << page_size;
return MergeFailureCode::MemAlignConsistencyCheck;
}
// COWs are always at least 2MB, this is guaranteed in snapshot creation.
std::unique_ptr<void, decltype(&::free)> buffer(addr, ::free);
if (!android::base::ReadFully(fd, buffer.get(), page_size)) {
PLOG(ERROR) << "Direct read failed " << cow_image_name;
return MergeFailureCode::DirectReadConsistencyCheck;
}
auto header = reinterpret_cast<CowHeader*>(buffer.get());
if (header->num_merge_ops != num_ops) {
LOG(ERROR) << "COW consistency check failed, expected " << num_ops << " to be merged, "
<< "but " << header->num_merge_ops << " were actually recorded.";
LOG(ERROR) << "Aborting merge progress for snapshot " << name
<< ", will try again next boot";
return MergeFailureCode::WrongMergeCountConsistencyCheck;
}
return MergeFailureCode::Ok;
}
MergeFailureCode SnapshotManager::MergeSecondPhaseSnapshots(LockedFile* lock) {
std::vector<std::string> snapshots;
if (!ListSnapshots(lock, &snapshots)) {
return MergeFailureCode::ListSnapshots;
}
SnapshotUpdateStatus update_status = ReadSnapshotUpdateStatus(lock);
CHECK(update_status.state() == UpdateState::Merging ||
update_status.state() == UpdateState::MergeFailed);
CHECK(update_status.merge_phase() == MergePhase::FIRST_PHASE);
update_status.set_state(UpdateState::Merging);
update_status.set_merge_phase(MergePhase::SECOND_PHASE);
if (!WriteSnapshotUpdateStatus(lock, update_status)) {
return MergeFailureCode::WriteStatus;
}
MergeFailureCode result = MergeFailureCode::Ok;
for (const auto& snapshot : snapshots) {
SnapshotStatus snapshot_status;
if (!ReadSnapshotStatus(lock, snapshot, &snapshot_status)) {
return MergeFailureCode::ReadStatus;
}
if (DecideMergePhase(snapshot_status) != MergePhase::SECOND_PHASE) {
continue;
}
auto code = SwitchSnapshotToMerge(lock, snapshot);
if (code != MergeFailureCode::Ok) {
LOG(ERROR) << "Failed to switch snapshot to a second-phase merge target: " << snapshot;
if (result == MergeFailureCode::Ok) {
result = code;
}
}
}
return result;
}
std::string SnapshotManager::GetBootSnapshotsWithoutSlotSwitchPath() {
return metadata_dir_ + "/" + android::base::Basename(kBootSnapshotsWithoutSlotSwitch);
}
std::string SnapshotManager::GetSnapshotBootIndicatorPath() {
return metadata_dir_ + "/" + android::base::Basename(kBootIndicatorPath);
}
std::string SnapshotManager::GetRollbackIndicatorPath() {
return metadata_dir_ + "/" + android::base::Basename(kRollbackIndicatorPath);
}
std::string SnapshotManager::GetForwardMergeIndicatorPath() {
return metadata_dir_ + "/allow-forward-merge";
}
std::string SnapshotManager::GetOldPartitionMetadataPath() {
return metadata_dir_ + "/old-partition-metadata";
}
void SnapshotManager::AcknowledgeMergeSuccess(LockedFile* lock) {
// It's not possible to remove update state in recovery, so write an
// indicator that cleanup is needed on reboot. If a factory data reset
// was requested, it doesn't matter, everything will get wiped anyway.
// To make testing easier we consider a /data wipe as cleaned up.
if (device_->IsRecovery()) {
WriteUpdateState(lock, UpdateState::MergeCompleted);
return;
}
RemoveAllUpdateState(lock);
if (UpdateUsesUserSnapshots(lock) && !device()->IsTestDevice()) {
if (snapuserd_client_) {
snapuserd_client_->DetachSnapuserd();
snapuserd_client_->RemoveTransitionedDaemonIndicator();
snapuserd_client_ = nullptr;
}
}
}
void SnapshotManager::AcknowledgeMergeFailure(MergeFailureCode failure_code) {
// Log first, so worst case, we always have a record of why the calls below
// were being made.
LOG(ERROR) << "Merge could not be completed and will be marked as failed.";
auto lock = LockExclusive();
if (!lock) return;
// Since we released the lock in between WaitForMerge and here, it's
// possible (1) the merge successfully completed or (2) was already
// marked as a failure. So make sure to check the state again, and
// only mark as a failure if appropriate.
UpdateState state = ReadUpdateState(lock.get());
if (state != UpdateState::Merging && state != UpdateState::MergeNeedsReboot) {
return;
}
WriteUpdateState(lock.get(), UpdateState::MergeFailed, failure_code);
}
bool SnapshotManager::OnSnapshotMergeComplete(LockedFile* lock, const std::string& name,
const SnapshotStatus& status) {
if (!UpdateUsesUserSnapshots(lock)) {
if (IsSnapshotDevice(name)) {
// We are extra-cautious here, to avoid deleting the wrong table.
std::string target_type;
DmTargetSnapshot::Status dm_status;
if (!QuerySnapshotStatus(name, &target_type, &dm_status)) {
return false;
}
if (target_type != "snapshot-merge") {
LOG(ERROR) << "Unexpected target type " << target_type
<< " for snapshot device: " << name;
return false;
}
if (dm_status.sectors_allocated != dm_status.metadata_sectors) {
LOG(ERROR) << "Merge is unexpectedly incomplete for device " << name;
return false;
}
if (!CollapseSnapshotDevice(lock, name, status)) {
LOG(ERROR) << "Unable to collapse snapshot: " << name;
return false;
}
}
} else {
// Just collapse the device - no need to query again as we just did
// prior to calling this function
if (!CollapseSnapshotDevice(lock, name, status)) {
LOG(ERROR) << "Unable to collapse snapshot: " << name;
return false;
}
}
// Note that collapsing is implicitly an Unmap, so we don't need to
// unmap the snapshot.
if (!DeleteSnapshot(lock, name)) {
LOG(ERROR) << "Could not delete snapshot: " << name;
return false;
}
return true;
}
bool SnapshotManager::CollapseSnapshotDevice(LockedFile* lock, const std::string& name,
const SnapshotStatus& status) {
if (!UpdateUsesUserSnapshots(lock)) {
// Verify we have a snapshot-merge device.
DeviceMapper::TargetInfo target;
if (!GetSingleTarget(name, TableQuery::Table, &target)) {
return false;
}
if (DeviceMapper::GetTargetType(target.spec) != "snapshot-merge") {
// This should be impossible, it was checked earlier.
LOG(ERROR) << "Snapshot device has invalid target type: " << name;
return false;
}
std::string base_device, cow_device;
if (!DmTargetSnapshot::GetDevicesFromParams(target.data, &base_device, &cow_device)) {
LOG(ERROR) << "Could not parse snapshot device " << name
<< " parameters: " << target.data;
return false;
}
}
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();
return false;
}
uint32_t slot = SlotNumberForSlotSuffix(device_->GetSlotSuffix());
// 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(base_device_params, &table)) {
LOG(ERROR) << "Could not create a DmTable for partition: " << name;
return false;
}
if (!dm_.LoadTableAndActivate(name, table)) {
return false;
}
if (!UpdateUsesUserSnapshots(lock)) {
// Attempt to delete the snapshot device if one still exists. Nothing
// should be depending on the device, and device-mapper should have
// flushed remaining I/O. We could in theory replace with dm-zero (or
// re-use the table above), but for now it's better to know why this
// would fail.
//
// Furthermore, we should not be trying to unmap for userspace snapshot
// as unmap will fail since dm-user itself was a snapshot device prior
// to switching of tables. Unmap will fail as the device will be mounted
// by system partitions
if (status.using_snapuserd()) {
auto dm_user_name = GetDmUserCowName(name, GetSnapshotDriver(lock));
UnmapDmUserDevice(dm_user_name);
}
}
// We can't delete base device immediately as daemon holds a reference.
// Make sure we wait for all the worker threads to terminate and release
// the reference
if (UpdateUsesUserSnapshots(lock) && EnsureSnapuserdConnected()) {
if (!snapuserd_client_->WaitForDeviceDelete(name)) {
LOG(ERROR) << "Failed to wait for " << name << " control device to delete";
}
}
auto base_name = GetBaseDeviceName(name);
if (!DeleteDeviceIfExists(base_name)) {
LOG(ERROR) << "Unable to delete base device for snapshot: " << base_name;
}
if (!DeleteDeviceIfExists(GetSourceDeviceName(name), 4000ms)) {
LOG(ERROR) << "Unable to delete source device for snapshot: " << GetSourceDeviceName(name);
}
return true;
}
bool SnapshotManager::HandleCancelledUpdate(LockedFile* lock,
const std::function<bool()>& before_cancel) {
auto slot = GetCurrentSlot();
if (slot == Slot::Unknown) {
return false;
}
// If all snapshots were reflashed, then cancel the entire update.
if (AreAllSnapshotsCancelled(lock)) {
LOG(WARNING) << "Detected re-flashing, cancelling unverified update.";
return RemoveAllUpdateState(lock, before_cancel);
}
// If update has been rolled back, then cancel the entire update.
// Client (update_engine) is responsible for doing additional cleanup work on its own states
// when ProcessUpdateState() returns UpdateState::Cancelled.
auto current_slot = GetCurrentSlot();
if (current_slot != Slot::Source) {
LOG(INFO) << "Update state is being processed while booting at " << current_slot
<< " slot, taking no action.";
return false;
}
// current_slot == Source. Attempt to detect rollbacks.
if (access(GetRollbackIndicatorPath().c_str(), F_OK) != 0) {
// This unverified update is not attempted. Take no action.
PLOG(INFO) << "Rollback indicator not detected. "
<< "Update state is being processed before reboot, taking no action.";
return false;
}
LOG(WARNING) << "Detected rollback, cancelling unverified update.";
return RemoveAllUpdateState(lock, before_cancel);
}
bool SnapshotManager::PerformInitTransition(InitTransition transition,
std::vector<std::string>* snapuserd_argv) {
LOG(INFO) << "Performing transition for snapuserd.";
// Don't use EnsureSnapuserdConnected() because this is called from init,
// and attempting to do so will deadlock.
if (!snapuserd_client_ && transition != InitTransition::SELINUX_DETACH) {
snapuserd_client_ = SnapuserdClient::Connect(kSnapuserdSocket, 10s);
if (!snapuserd_client_) {
LOG(ERROR) << "Unable to connect to snapuserd";
return false;
}
}
auto lock = LockExclusive();
if (!lock) return false;
std::vector<std::string> snapshots;
if (!ListSnapshots(lock.get(), &snapshots)) {
LOG(ERROR) << "Failed to list snapshots.";
return false;
}
if (UpdateUsesUserSnapshots(lock.get()) && transition == InitTransition::SELINUX_DETACH) {
snapuserd_argv->emplace_back("-user_snapshot");
if (UpdateUsesIouring(lock.get())) {
snapuserd_argv->emplace_back("-io_uring");
}
}
size_t num_cows = 0;
size_t ok_cows = 0;
for (const auto& snapshot : snapshots) {
std::string user_cow_name = GetDmUserCowName(snapshot, GetSnapshotDriver(lock.get()));
if (dm_.GetState(user_cow_name) == DmDeviceState::INVALID) {
continue;
}
DeviceMapper::TargetInfo target;
if (!GetSingleTarget(user_cow_name, TableQuery::Table, &target)) {
continue;
}
auto target_type = DeviceMapper::GetTargetType(target.spec);
if (target_type != "user") {
LOG(ERROR) << "Unexpected target type for " << user_cow_name << ": " << target_type;
continue;
}
num_cows++;
SnapshotStatus snapshot_status;
if (!ReadSnapshotStatus(lock.get(), snapshot, &snapshot_status)) {
LOG(ERROR) << "Unable to read snapshot status: " << snapshot;
continue;
}
auto misc_name = user_cow_name;
std::string source_device_name;
if (snapshot_status.old_partition_size() > 0) {
source_device_name = GetSourceDeviceName(snapshot);
} else {
source_device_name = GetBaseDeviceName(snapshot);
}
std::string source_device;
if (!dm_.GetDmDevicePathByName(source_device_name, &source_device)) {
LOG(ERROR) << "Could not get device path for " << GetSourceDeviceName(snapshot);
continue;
}
std::string base_path_merge;
if (!dm_.GetDmDevicePathByName(GetBaseDeviceName(snapshot), &base_path_merge)) {
LOG(ERROR) << "Could not get device path for " << GetSourceDeviceName(snapshot);
continue;
}
std::string cow_image_name = GetMappedCowDeviceName(snapshot, snapshot_status);
std::string cow_image_device;
if (!dm_.GetDmDevicePathByName(cow_image_name, &cow_image_device)) {
LOG(ERROR) << "Could not get device path for " << cow_image_name;
continue;
}
if (transition == InitTransition::SELINUX_DETACH) {
if (!UpdateUsesUserSnapshots(lock.get())) {
auto message = misc_name + "," + cow_image_device + "," + source_device;
snapuserd_argv->emplace_back(std::move(message));
} else {
auto message = misc_name + "," + cow_image_device + "," + source_device + "," +
base_path_merge;
snapuserd_argv->emplace_back(std::move(message));
}
// Do not attempt to connect to the new snapuserd yet, it hasn't
// been started. We do however want to wait for the misc device
// to have been created.
ok_cows++;
continue;
}
DmTable table;
table.Emplace<DmTargetUser>(0, target.spec.length, misc_name);
if (!dm_.LoadTableAndActivate(user_cow_name, table)) {
LOG(ERROR) << "Unable to swap tables for " << misc_name;
continue;
}
// Wait for ueventd to acknowledge and create the control device node.
std::string control_device = "/dev/dm-user/" + misc_name;
if (!WaitForDevice(control_device, 10s)) {
LOG(ERROR) << "dm-user control device no found: " << misc_name;
continue;
}
uint64_t base_sectors;
if (!UpdateUsesUserSnapshots(lock.get())) {
base_sectors =
snapuserd_client_->InitDmUserCow(misc_name, cow_image_device, source_device);
} else {
base_sectors = snapuserd_client_->InitDmUserCow(misc_name, cow_image_device,
source_device, base_path_merge);
}
if (base_sectors == 0) {
// Unrecoverable as metadata reads from cow device failed
LOG(FATAL) << "Failed to retrieve base_sectors from Snapuserd";
return false;
}
CHECK(base_sectors <= target.spec.length);
if (!snapuserd_client_->AttachDmUser(misc_name)) {
// This error is unrecoverable. We cannot proceed because reads to
// the underlying device will fail.
LOG(FATAL) << "Could not initialize snapuserd for " << user_cow_name;
return false;
}
ok_cows++;
}
if (ok_cows != num_cows) {
LOG(ERROR) << "Could not transition all snapuserd consumers.";
return false;
}
return true;
}
std::unique_ptr<LpMetadata> SnapshotManager::ReadCurrentMetadata() {
const auto& opener = device_->GetPartitionOpener();
uint32_t slot = SlotNumberForSlotSuffix(device_->GetSlotSuffix());
auto super_device = device_->GetSuperDevice(slot);
auto metadata = android::fs_mgr::ReadMetadata(opener, super_device, slot);
if (!metadata) {
LOG(ERROR) << "Could not read dynamic partition metadata for device: " << super_device;
return nullptr;
}
return metadata;
}
SnapshotManager::MetadataPartitionState SnapshotManager::GetMetadataPartitionState(
const LpMetadata& metadata, const std::string& name) {
auto partition = android::fs_mgr::FindPartition(metadata, name);
if (!partition) return MetadataPartitionState::None;
if (partition->attributes & LP_PARTITION_ATTR_UPDATED) {
return MetadataPartitionState::Updated;
}
return MetadataPartitionState::Flashed;
}
bool SnapshotManager::AreAllSnapshotsCancelled(LockedFile* lock) {
std::vector<std::string> snapshots;
if (!ListSnapshots(lock, &snapshots)) {
LOG(WARNING) << "Failed to list snapshots to determine whether device has been flashed "
<< "after applying an update. Assuming no snapshots.";
// Let HandleCancelledUpdate resets UpdateState.
return true;
}
std::map<std::string, bool> flashing_status;
if (!GetSnapshotFlashingStatus(lock, snapshots, &flashing_status)) {
LOG(WARNING) << "Failed to determine whether partitions have been flashed. Not"
<< "removing update states.";
return false;
}
bool all_snapshots_cancelled = std::all_of(flashing_status.begin(), flashing_status.end(),
[](const auto& pair) { return pair.second; });
if (all_snapshots_cancelled) {
LOG(WARNING) << "All partitions are re-flashed after update, removing all update states.";
}
return all_snapshots_cancelled;
}
bool SnapshotManager::GetSnapshotFlashingStatus(LockedFile* lock,
const std::vector<std::string>& snapshots,
std::map<std::string, bool>* out) {
CHECK(lock);
auto source_slot_suffix = ReadUpdateSourceSlotSuffix();
if (source_slot_suffix.empty()) {
return false;
}
uint32_t source_slot = SlotNumberForSlotSuffix(source_slot_suffix);
uint32_t target_slot = (source_slot == 0) ? 1 : 0;
// Attempt to detect re-flashing on each partition.
// - If all partitions are re-flashed, we can proceed to cancel the whole update.
// - If only some of the partitions are re-flashed, snapshots for re-flashed partitions are
// deleted. Caller is responsible for merging the rest of the snapshots.
// - If none of the partitions are re-flashed, caller is responsible for merging the snapshots.
//
// Note that we use target slot metadata, since if an OTA has been applied
// to the target slot, we can detect the UPDATED flag. Any kind of flash
// operation against dynamic partitions ensures that all copies of the
// metadata are in sync, so flashing all partitions on the source slot will
// remove the UPDATED flag on the target slot as well.
const auto& opener = device_->GetPartitionOpener();
auto super_device = device_->GetSuperDevice(target_slot);
auto metadata = android::fs_mgr::ReadMetadata(opener, super_device, target_slot);
if (!metadata) {
return false;
}
for (const auto& snapshot_name : snapshots) {
if (GetMetadataPartitionState(*metadata, snapshot_name) ==
MetadataPartitionState::Updated) {
out->emplace(snapshot_name, false);
} else {
// Delete snapshots for partitions that are re-flashed after the update.
LOG(WARNING) << "Detected re-flashing of partition " << snapshot_name << ".";
out->emplace(snapshot_name, true);
}
}
return true;
}
void SnapshotManager::RemoveInvalidSnapshots(LockedFile* lock) {
std::vector<std::string> snapshots;
// Remove the stale snapshot metadata
//
// We make sure that all the three cases
// are valid before removing the snapshot metadata:
//
// 1: dm state is active
// 2: Root fs is not mounted off as a snapshot device
// 3: Snapshot slot suffix should match current device slot
if (!ListSnapshots(lock, &snapshots, device_->GetSlotSuffix()) || snapshots.empty()) {
return;
}
// We indeed have some invalid snapshots
for (const auto& name : snapshots) {
if (dm_.GetState(name) == DmDeviceState::ACTIVE && !IsSnapshotDevice(name)) {
if (!DeleteSnapshot(lock, name)) {
LOG(ERROR) << "Failed to delete invalid snapshot: " << name;
} else {
LOG(INFO) << "Invalid snapshot: " << name << " deleted";
}
}
}
}
bool SnapshotManager::RemoveAllSnapshots(LockedFile* lock) {
std::vector<std::string> snapshots;
if (!ListSnapshots(lock, &snapshots)) {
LOG(ERROR) << "Could not list snapshots";
return false;
}
std::map<std::string, bool> flashing_status;
if (!GetSnapshotFlashingStatus(lock, snapshots, &flashing_status)) {
LOG(WARNING) << "Failed to get flashing status";
}
auto current_slot = GetCurrentSlot();
bool ok = true;
bool has_mapped_cow_images = false;
for (const auto& name : snapshots) {
// If booting off source slot, it is okay to unmap and delete all the snapshots.
// If boot indicator is missing, update state is None or Initiated, so
// it is also okay to unmap and delete all the snapshots.
// If booting off target slot,
// - should not unmap because:
// - In Android mode, snapshots are not mapped, but
// filesystems are mounting off dm-linear targets directly.
// - In recovery mode, assume nothing is mapped, so it is optional to unmap.
// - If partition is flashed or unknown, it is okay to delete snapshots.
// Otherwise (UPDATED flag), only delete snapshots if they are not mapped
// as dm-snapshot (for example, after merge completes).
bool should_unmap = current_slot != Slot::Target;
bool should_delete = ShouldDeleteSnapshot(flashing_status, current_slot, name);
if (should_unmap && android::base::EndsWith(name, device_->GetSlotSuffix())) {
// Something very unexpected has happened - we want to unmap this
// snapshot, but it's on the wrong slot. We can't unmap an active
// partition. If this is not really a snapshot, skip the unmap
// step.
if (dm_.GetState(name) == DmDeviceState::INVALID || !IsSnapshotDevice(name)) {
LOG(ERROR) << "Detected snapshot " << name << " on " << current_slot << " slot"
<< " for source partition; removing without unmap.";
should_unmap = false;
}
}
bool partition_ok = true;
if (should_unmap && !UnmapPartitionWithSnapshot(lock, name)) {
partition_ok = false;
}
if (partition_ok && should_delete && !DeleteSnapshot(lock, name)) {
partition_ok = false;
}
if (!partition_ok) {
// Remember whether or not we were able to unmap the cow image.
auto cow_image_device = GetCowImageDeviceName(name);
has_mapped_cow_images |=
(EnsureImageManager() && images_->IsImageMapped(cow_image_device));
ok = false;
}
}
if (ok || !has_mapped_cow_images) {
// Delete any image artifacts as a precaution, in case an update is
// being cancelled due to some corrupted state in an lp_metadata file.
// Note that we do not do this if some cow images are still mapped,
// since we must not remove backing storage if it's in use.
if (!EnsureImageManager() || !images_->RemoveAllImages()) {
LOG(ERROR) << "Could not remove all snapshot artifacts";
return false;
}
}
return ok;
}
// See comments in RemoveAllSnapshots().
bool SnapshotManager::ShouldDeleteSnapshot(const std::map<std::string, bool>& flashing_status,
Slot current_slot, const std::string& name) {
if (current_slot != Slot::Target) {
return true;
}
auto it = flashing_status.find(name);
if (it == flashing_status.end()) {
LOG(WARNING) << "Can't determine flashing status for " << name;
return true;
}
if (it->second) {
// partition flashed, okay to delete obsolete snapshots
return true;
}
return !IsSnapshotDevice(name);
}
UpdateState SnapshotManager::GetUpdateState(double* progress) {
// If we've never started an update, the state file won't exist.
auto state_file = GetStateFilePath();
if (access(state_file.c_str(), F_OK) != 0 && errno == ENOENT) {
return UpdateState::None;
}
auto lock = LockShared();
if (!lock) {
return UpdateState::None;
}
SnapshotUpdateStatus update_status = ReadSnapshotUpdateStatus(lock.get());
auto state = update_status.state();
if (progress == nullptr) {
return state;
}
if (state == UpdateState::MergeCompleted) {
*progress = 100.0;
return state;
}
*progress = 0.0;
if (state != UpdateState::Merging) {
return state;
}
if (!UpdateUsesUserSnapshots(lock.get())) {
// Sum all the snapshot states as if the system consists of a single huge
// snapshots device, then compute the merge completion percentage of that
// device.
std::vector<std::string> snapshots;
if (!ListSnapshots(lock.get(), &snapshots)) {
LOG(ERROR) << "Could not list snapshots";
return state;
}
DmTargetSnapshot::Status fake_snapshots_status = {};
for (const auto& snapshot : snapshots) {
DmTargetSnapshot::Status current_status;
if (!IsSnapshotDevice(snapshot)) continue;
if (!QuerySnapshotStatus(snapshot, nullptr, &current_status)) continue;
fake_snapshots_status.sectors_allocated += current_status.sectors_allocated;
fake_snapshots_status.total_sectors += current_status.total_sectors;
fake_snapshots_status.metadata_sectors += current_status.metadata_sectors;
}
*progress = DmTargetSnapshot::MergePercent(fake_snapshots_status,
update_status.sectors_allocated());
} else {
if (EnsureSnapuserdConnected()) {
*progress = snapuserd_client_->GetMergePercent();
}
}
return state;
}
bool SnapshotManager::IsSnapshotWithoutSlotSwitch() {
return (access(GetBootSnapshotsWithoutSlotSwitchPath().c_str(), F_OK) == 0);
}
bool SnapshotManager::UpdateUsesCompression() {
auto lock = LockShared();
if (!lock) return false;
return UpdateUsesCompression(lock.get());
}
bool SnapshotManager::UpdateUsesCompression(LockedFile* lock) {
// This returns true even if compression is "none", since update_engine is
// really just trying to see if snapuserd is in use.
SnapshotUpdateStatus update_status = ReadSnapshotUpdateStatus(lock);
return update_status.using_snapuserd();
}
bool SnapshotManager::UpdateUsesIouring(LockedFile* lock) {
SnapshotUpdateStatus update_status = ReadSnapshotUpdateStatus(lock);
return update_status.io_uring_enabled();
}
bool SnapshotManager::UpdateUsesUserSnapshots() {
// This and the following function is constantly
// invoked during snapshot merge. We want to avoid
// constantly reading from disk. Hence, store this
// value in memory.
//
// Furthermore, this value in the disk is set
// only when OTA is applied and doesn't change
// during merge phase. Hence, once we know that
// the value is read from disk the very first time,
// it is safe to read successive checks from memory.
if (is_snapshot_userspace_.has_value()) {
return is_snapshot_userspace_.value();
}
auto lock = LockShared();
if (!lock) return false;
return UpdateUsesUserSnapshots(lock.get());
}
bool SnapshotManager::UpdateUsesUserSnapshots(LockedFile* lock) {
// See UpdateUsesUserSnapshots()
if (is_snapshot_userspace_.has_value()) {
return is_snapshot_userspace_.value();
}
SnapshotUpdateStatus update_status = ReadSnapshotUpdateStatus(lock);
is_snapshot_userspace_ = update_status.userspace_snapshots();
return is_snapshot_userspace_.value();
}
bool SnapshotManager::ListSnapshots(LockedFile* lock, std::vector<std::string>* snapshots,
const std::string& suffix) {
CHECK(lock);
auto dir_path = metadata_dir_ + "/snapshots"s;
std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(dir_path.c_str()), closedir);
if (!dir) {
PLOG(ERROR) << "opendir failed: " << dir_path;
return false;
}
struct dirent* dp;
while ((dp = readdir(dir.get())) != nullptr) {
if (dp->d_type != DT_REG) continue;
std::string name(dp->d_name);
if (!suffix.empty() && !android::base::EndsWith(name, suffix)) {
continue;
}
// Insert system and product partition at the beginning so that
// during snapshot-merge, these partitions are merged first.
if (name == "system_a" || name == "system_b" || name == "product_a" ||
name == "product_b") {
snapshots->insert(snapshots->begin(), std::move(name));
} else {
snapshots->emplace_back(std::move(name));
}
}
return true;
}
bool SnapshotManager::IsSnapshotManagerNeeded() {
return access(kBootIndicatorPath, F_OK) == 0;
}
std::string SnapshotManager::GetGlobalRollbackIndicatorPath() {
return kRollbackIndicatorPath;
}
bool SnapshotManager::NeedSnapshotsInFirstStageMount() {
if (IsSnapshotWithoutSlotSwitch()) {
if (GetCurrentSlot() != Slot::Source) {
LOG(ERROR) << "Snapshots marked to boot without slot switch; but slot is wrong";
return false;
}
return true;
}
// If we fail to read, we'll wind up using CreateLogicalPartitions, which
// will create devices that look like the old slot, except with extra
// content at the end of each device. This will confuse dm-verity, and
// ultimately we'll fail to boot. Why not make it a fatal error and have
// the reason be clearer? Because the indicator file still exists, and
// if this was FATAL, reverting to the old slot would be broken.
auto slot = GetCurrentSlot();
if (slot != Slot::Target) {
if (slot == Slot::Source) {
// Device is rebooting into the original slot, so mark this as a
// rollback.
auto path = GetRollbackIndicatorPath();
if (!android::base::WriteStringToFile("1", path)) {
PLOG(ERROR) << "Unable to write rollback indicator: " << path;
} else {
LOG(INFO) << "Rollback detected, writing rollback indicator to " << path;
}
}
LOG(INFO) << "Not booting from new slot. Will not mount snapshots.";
return false;
}
// If we can't read the update state, it's unlikely anything else will
// succeed, so this is a fatal error. We'll eventually exhaust boot
// attempts and revert to the old slot.
auto lock = LockShared();
if (!lock) {
LOG(FATAL) << "Could not read update state to determine snapshot status";
return false;
}
switch (ReadUpdateState(lock.get())) {
case UpdateState::Unverified:
case UpdateState::Merging:
case UpdateState::MergeFailed:
return true;
default:
return false;
}
}
bool SnapshotManager::CreateLogicalAndSnapshotPartitions(
const std::string& super_device, const std::chrono::milliseconds& timeout_ms) {
LOG(INFO) << "Creating logical partitions with snapshots as needed";
auto lock = LockExclusive();
if (!lock) return false;
uint32_t slot = SlotNumberForSlotSuffix(device_->GetSlotSuffix());
return MapAllPartitions(lock.get(), super_device, slot, timeout_ms);
}
bool SnapshotManager::MapAllPartitions(LockedFile* lock, const std::string& super_device,
uint32_t slot, const std::chrono::milliseconds& timeout_ms) {
const auto& opener = device_->GetPartitionOpener();
auto metadata = android::fs_mgr::ReadMetadata(opener, super_device, slot);
if (!metadata) {
LOG(ERROR) << "Could not read dynamic partition metadata for device: " << super_device;
return false;
}
if (!EnsureImageManager()) {
return false;
}
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(),
.partition = &partition,
.timeout_ms = timeout_ms,
.partition_opener = &opener,
};
if (!MapPartitionWithSnapshot(lock, std::move(params), SnapshotContext::Mount, nullptr)) {
return false;
}
}
LOG(INFO) << "Created logical partitions with snapshot.";
return true;
}
static std::chrono::milliseconds GetRemainingTime(
const std::chrono::milliseconds& timeout,
const std::chrono::time_point<std::chrono::steady_clock>& begin) {
// If no timeout is specified, execute all commands without specifying any timeout.
if (timeout.count() == 0) return std::chrono::milliseconds(0);
auto passed_time = std::chrono::steady_clock::now() - begin;
auto remaining_time = timeout - duration_cast<std::chrono::milliseconds>(passed_time);
if (remaining_time.count() <= 0) {
LOG(ERROR) << "MapPartitionWithSnapshot has reached timeout " << timeout.count() << "ms ("
<< remaining_time.count() << "ms remaining)";
// Return min() instead of remaining_time here because 0 is treated as a special value for
// no timeout, where the rest of the commands will still be executed.
return std::chrono::milliseconds::min();
}
return remaining_time;
}
bool SnapshotManager::MapPartitionWithSnapshot(LockedFile* lock,
CreateLogicalPartitionParams params,
SnapshotContext context, SnapshotPaths* paths) {
auto begin = std::chrono::steady_clock::now();
CHECK(lock);
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;
if (!params.InitDefaults(&params_owned_data)) {
return false;
}
if (!params.partition->num_extents) {
LOG(INFO) << "Skipping zero-length logical partition: " << params.GetPartitionName();
return true; // leave path empty to indicate that nothing is mapped.
}
// Determine if there is a live snapshot for the SnapshotStatus of the partition; i.e. if the
// partition still has a snapshot that needs to be mapped. If no live snapshot or merge
// completed, live_snapshot_status is set to nullopt.
std::optional<SnapshotStatus> live_snapshot_status;
do {
if (!IsSnapshotWithoutSlotSwitch() &&
!(params.partition->attributes & LP_PARTITION_ATTR_UPDATED)) {
LOG(INFO) << "Detected re-flashing of partition, will skip snapshot: "
<< params.GetPartitionName();
break;
}
auto file_path = GetSnapshotStatusFilePath(params.GetPartitionName());
if (access(file_path.c_str(), F_OK) != 0) {
if (errno != ENOENT) {
PLOG(INFO) << "Can't map snapshot for " << params.GetPartitionName()
<< ": Can't access " << file_path;
return false;
}
break;
}
live_snapshot_status = std::make_optional<SnapshotStatus>();
if (!ReadSnapshotStatus(lock, params.GetPartitionName(), &*live_snapshot_status)) {
return false;
}
// No live snapshot if merge is completed.
if (live_snapshot_status->state() == SnapshotState::MERGE_COMPLETED) {
live_snapshot_status.reset();
}
if (live_snapshot_status->state() == SnapshotState::NONE ||
live_snapshot_status->cow_partition_size() + live_snapshot_status->cow_file_size() ==
0) {
LOG(WARNING) << "Snapshot status for " << params.GetPartitionName()
<< " is invalid, ignoring: state = "
<< SnapshotState_Name(live_snapshot_status->state())
<< ", cow_partition_size = " << live_snapshot_status->cow_partition_size()
<< ", cow_file_size = " << live_snapshot_status->cow_file_size();
live_snapshot_status.reset();
}
} while (0);
if (live_snapshot_status.has_value()) {
// dm-snapshot requires the base device to be writable.
params.force_writable = true;
// Map the base device with a different name to avoid collision.
params.device_name = GetBaseDeviceName(params.GetPartitionName());
}
AutoDeviceList created_devices;
// Create the base device for the snapshot, or if there is no snapshot, the
// device itself. This device consists of the real blocks in the super
// partition that this logical partition occupies.
std::string base_path;
if (!CreateLogicalPartition(params, &base_path)) {
LOG(ERROR) << "Could not create logical partition " << params.GetPartitionName()
<< " as device " << params.GetDeviceName();
return false;
}
created_devices.EmplaceBack<AutoUnmapDevice>(&dm_, params.GetDeviceName());
if (paths) {
paths->target_device = base_path;
}
auto remaining_time = GetRemainingTime(params.timeout_ms, begin);
if (remaining_time.count() < 0) {
return false;
}
// Wait for the base device to appear
if (!WaitForDevice(base_path, remaining_time)) {
return false;
}
if (!live_snapshot_status.has_value()) {
created_devices.Release();
return true;
}
// We don't have ueventd in first-stage init, so use device major:minor
// strings instead.
std::string base_device;
if (!dm_.GetDeviceString(params.GetDeviceName(), &base_device)) {
LOG(ERROR) << "Could not determine major/minor for: " << params.GetDeviceName();
return false;
}
remaining_time = GetRemainingTime(params.timeout_ms, begin);
if (remaining_time.count() < 0) return false;
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 (!GetMappedImageDeviceStringOrPath(cow_name, &cow_device)) {
LOG(ERROR) << "Could not determine major/minor for: " << cow_name;
return false;
}
if (paths) {
paths->cow_device_name = cow_name;
}
remaining_time = GetRemainingTime(params.timeout_ms, begin);
if (remaining_time.count() < 0) return false;
if (context == SnapshotContext::Update && live_snapshot_status->using_snapuserd()) {
// Stop here, we can't run dm-user yet, the COW isn't built.
created_devices.Release();
return true;
}
if (live_snapshot_status->using_snapuserd()) {
// Get the source device (eg the view of the partition from before it was resized).
std::string source_device_path;
if (live_snapshot_status->old_partition_size() > 0) {
if (!MapSourceDevice(lock, params.GetPartitionName(), remaining_time,
&source_device_path)) {
LOG(ERROR) << "Could not map source device for: " << cow_name;
return false;
}
auto source_device = GetSourceDeviceName(params.GetPartitionName());
created_devices.EmplaceBack<AutoUnmapDevice>(&dm_, source_device);
} else {
source_device_path = base_path;
}
if (!WaitForDevice(source_device_path, remaining_time)) {
return false;
}
std::string cow_path;
if (!GetMappedImageDevicePath(cow_name, &cow_path)) {
LOG(ERROR) << "Could not determine path for: " << cow_name;
return false;
}
if (!WaitForDevice(cow_path, remaining_time)) {
return false;
}
auto name = GetDmUserCowName(params.GetPartitionName(), GetSnapshotDriver(lock));
std::string new_cow_device;
if (!MapDmUserCow(lock, name, cow_path, source_device_path, base_path, remaining_time,
&new_cow_device)) {
LOG(ERROR) << "Could not map dm-user device for partition "
<< params.GetPartitionName();
return false;
}
created_devices.EmplaceBack<AutoUnmapDevice>(&dm_, name);
cow_device = new_cow_device;
}
// For userspace snapshots, dm-user block device itself will act as a
// snapshot device. There is one subtle difference - MapSnapshot will create
// either snapshot target or snapshot-merge target based on the underlying
// state of the snapshot device. If snapshot-merge target is created, merge
// will immediately start in the kernel.
//
// This is no longer true with respect to userspace snapshots. When dm-user
// block device is created, we just have the snapshots ready but daemon in
// the user-space will not start the merge. We have to explicitly inform the
// daemon to resume the merge. Check ProcessUpdateState() call stack.
if (!UpdateUsesUserSnapshots(lock)) {
remaining_time = GetRemainingTime(params.timeout_ms, begin);
if (remaining_time.count() < 0) return false;
std::string path;
if (!MapSnapshot(lock, params.GetPartitionName(), base_device, cow_device, remaining_time,
&path)) {
LOG(ERROR) << "Could not map snapshot for partition: " << params.GetPartitionName();
return false;
}
// No need to add params.GetPartitionName() to created_devices since it is immediately
// released.
if (paths) {
paths->snapshot_device = path;
}
LOG(INFO) << "Mapped " << params.GetPartitionName() << " as snapshot device at " << path;
} else {
LOG(INFO) << "Mapped " << params.GetPartitionName() << " as snapshot device at "
<< cow_device;
}
created_devices.Release();
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 base_name = GetBaseDeviceName(target_partition_name);
if (!DeleteDeviceIfExists(base_name)) {
LOG(ERROR) << "Cannot delete base device: " << base_name;
return false;
}
auto source_name = GetSourceDeviceName(target_partition_name);
if (!DeleteDeviceIfExists(source_name)) {
LOG(ERROR) << "Cannot delete source device: " << source_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);
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);
// Map COW image if necessary.
if (snapshot_status.cow_file_size() > 0) {
if (!EnsureImageManager()) return false;
auto remaining_time = GetRemainingTime(params.timeout_ms, begin);
if (remaining_time.count() < 0) return false;
if (!MapCowImage(partition_name, remaining_time).has_value()) {
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 (!GetMappedImageDeviceStringOrPath(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;
if (UpdateUsesCompression(lock) && !UpdateUsesUserSnapshots(lock)) {
auto dm_user_name = GetDmUserCowName(name, GetSnapshotDriver(lock));
if (!UnmapDmUserDevice(dm_user_name)) {
return false;
}
}
if (!DeleteDeviceIfExists(GetCowName(name), 4000ms)) {
LOG(ERROR) << "Cannot unmap: " << GetCowName(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;
}
bool SnapshotManager::UnmapDmUserDevice(const std::string& dm_user_name) {
if (dm_.GetState(dm_user_name) == DmDeviceState::INVALID) {
return true;
}
if (!DeleteDeviceIfExists(dm_user_name)) {
LOG(ERROR) << "Cannot unmap " << dm_user_name;
return false;
}
if (EnsureSnapuserdConnected()) {
if (!snapuserd_client_->WaitForDeviceDelete(dm_user_name)) {
LOG(ERROR) << "Failed to wait for " << dm_user_name << " control device to delete";
return false;
}
}