snapshot metadata files are stored in /metadata. This means, we cannot wipe after installing any update. This patch does the following: 1: Create a scratch space in super partition. The scratch space for ota metadata is just about 1MB. 2: Create ext4 filesystem on top of scratch block device. 3: Mount the scratch on /mnt/scratch_super 4: When snapshot-manager instance is created, point the /mnt/scratch/ota to metadata_dir_ so that all the snapshot files are stored in the new path. All the logic of OTA remains the same. This flow is enabled only on userdebug builds for now and the only consumer would be snapshotctl $snapshotctl apply-update /data/nbd/ -w During init, we would have to mount the scratch partition to detect if there is any pending updates. With this, we would now be able to wipe the device along with the update flow. This will help incremental flashing wherein we would end up saving ~35-40 seconds on Pixel devices. With this flow, the end-to-end update for incremental builds takes ~20-30 seconds. Bug: 330744468 Test: Pixel 6 incremental flashing with wipe, Full OTA, vts_libsnapshot Change-Id: Iac6ce2cf37b70ea221cd18175c8962988d03d95b Signed-off-by: Akilesh Kailash <akailash@google.com>
844 lines
28 KiB
C++
844 lines
28 KiB
C++
//
|
|
// 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 <sysexits.h>
|
|
#include <unistd.h>
|
|
#include <chrono>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <future>
|
|
#include <iostream>
|
|
#include <map>
|
|
#include <sstream>
|
|
#include <thread>
|
|
|
|
#include <android-base/file.h>
|
|
#include <android-base/logging.h>
|
|
#include <android-base/unique_fd.h>
|
|
|
|
#include <android-base/chrono_utils.h>
|
|
#include <android-base/parseint.h>
|
|
#include <android-base/properties.h>
|
|
#include <android-base/scopeguard.h>
|
|
#include <android-base/stringprintf.h>
|
|
#include <android-base/strings.h>
|
|
|
|
#include <fs_mgr.h>
|
|
#include <fs_mgr_dm_linear.h>
|
|
#include <fstab/fstab.h>
|
|
#include <liblp/builder.h>
|
|
#include <libsnapshot/cow_format.h>
|
|
#include <libsnapshot/snapshot.h>
|
|
#include <storage_literals/storage_literals.h>
|
|
|
|
#include "partition_cow_creator.h"
|
|
#include "scratch_super.h"
|
|
|
|
#ifdef SNAPSHOTCTL_USERDEBUG_OR_ENG
|
|
#include <BootControlClient.h>
|
|
#endif
|
|
|
|
using namespace std::chrono_literals;
|
|
using namespace std::string_literals;
|
|
using namespace android::storage_literals;
|
|
using android::base::LogdLogger;
|
|
using android::base::StderrLogger;
|
|
using android::base::TeeLogger;
|
|
using namespace android::dm;
|
|
using namespace android::fs_mgr;
|
|
using android::fs_mgr::CreateLogicalPartitionParams;
|
|
using android::fs_mgr::FindPartition;
|
|
using android::fs_mgr::GetPartitionSize;
|
|
using android::fs_mgr::PartitionOpener;
|
|
using android::fs_mgr::ReadMetadata;
|
|
using android::fs_mgr::SlotNumberForSlotSuffix;
|
|
|
|
int Usage() {
|
|
std::cerr << "snapshotctl: Control snapshots.\n"
|
|
"Usage: snapshotctl [action] [flags]\n"
|
|
"Actions:\n"
|
|
" dump\n"
|
|
" Print snapshot states.\n"
|
|
" merge\n"
|
|
" Deprecated.\n"
|
|
" map\n"
|
|
" Map all partitions at /dev/block/mapper\n"
|
|
" map-snapshots <directory where snapshot patches are present>\n"
|
|
" Map all snapshots based on patches present in the directory\n"
|
|
" unmap-snapshots\n"
|
|
" Unmap all pre-created snapshots\n"
|
|
" delete-snapshots\n"
|
|
" Delete all pre-created snapshots\n"
|
|
" revert-snapshots\n"
|
|
" Prepares devices to boot without snapshots on next boot.\n"
|
|
" This does not delete the snapshot. It only removes the indicators\n"
|
|
" so that first stage init will not mount from snapshots.\n"
|
|
" apply-update\n"
|
|
" Apply the incremental OTA update wherein the snapshots are\n"
|
|
" directly written to COW block device. This will bypass update-engine\n"
|
|
" and the device will be ready to boot from the target build.\n";
|
|
return EX_USAGE;
|
|
}
|
|
|
|
namespace android {
|
|
namespace snapshot {
|
|
|
|
#ifdef SNAPSHOTCTL_USERDEBUG_OR_ENG
|
|
class MapSnapshots {
|
|
public:
|
|
MapSnapshots(std::string path = "", bool metadata_super = false);
|
|
bool CreateSnapshotDevice(std::string& partition_name, std::string& patch);
|
|
bool InitiateThreadedSnapshotWrite(std::string& pname, std::string& snapshot_patch);
|
|
bool FinishSnapshotWrites();
|
|
bool UnmapCowImagePath(std::string& name);
|
|
bool DeleteSnapshots();
|
|
bool CleanupSnapshot() { return sm_->PrepareDeviceToBootWithoutSnapshot(); }
|
|
bool BeginUpdate();
|
|
bool ApplyUpdate();
|
|
|
|
private:
|
|
std::optional<std::string> GetCowImagePath(std::string& name);
|
|
bool PrepareUpdate();
|
|
bool GetCowDevicePath(std::string partition_name, std::string* cow_path);
|
|
bool WriteSnapshotPatch(std::string cow_device, std::string patch);
|
|
std::string GetGroupName(const android::fs_mgr::LpMetadata& pt,
|
|
const std::string& partiton_name);
|
|
std::unique_ptr<SnapshotManager::LockedFile> lock_;
|
|
std::unique_ptr<SnapshotManager> sm_;
|
|
std::vector<std::future<bool>> threads_;
|
|
std::string snapshot_dir_path_;
|
|
std::unordered_map<std::string, chromeos_update_engine::DynamicPartitionGroup*> group_map_;
|
|
|
|
std::vector<std::string> patchfiles_;
|
|
chromeos_update_engine::DeltaArchiveManifest manifest_;
|
|
bool metadata_super_ = false;
|
|
};
|
|
|
|
MapSnapshots::MapSnapshots(std::string path, bool metadata_super) {
|
|
snapshot_dir_path_ = path + "/";
|
|
metadata_super_ = metadata_super;
|
|
}
|
|
|
|
std::string MapSnapshots::GetGroupName(const android::fs_mgr::LpMetadata& pt,
|
|
const std::string& partition_name) {
|
|
std::string group_name;
|
|
for (const auto& partition : pt.partitions) {
|
|
std::string name = android::fs_mgr::GetPartitionName(partition);
|
|
auto suffix = android::fs_mgr::GetPartitionSlotSuffix(name);
|
|
std::string pname = name.substr(0, name.size() - suffix.size());
|
|
if (pname == partition_name) {
|
|
std::string group_name =
|
|
android::fs_mgr::GetPartitionGroupName(pt.groups[partition.group_index]);
|
|
return group_name.substr(0, group_name.size() - suffix.size());
|
|
}
|
|
}
|
|
return "";
|
|
}
|
|
|
|
bool MapSnapshots::PrepareUpdate() {
|
|
if (metadata_super_ && !CreateScratchOtaMetadataOnSuper()) {
|
|
LOG(ERROR) << "Failed to create OTA metadata on super";
|
|
return false;
|
|
}
|
|
sm_ = SnapshotManager::New();
|
|
|
|
auto source_slot = fs_mgr_get_slot_suffix();
|
|
auto source_slot_number = SlotNumberForSlotSuffix(source_slot);
|
|
auto super_source = fs_mgr_get_super_partition_name(source_slot_number);
|
|
|
|
// Get current partition information.
|
|
PartitionOpener opener;
|
|
auto source_metadata = ReadMetadata(opener, super_source, source_slot_number);
|
|
if (!source_metadata) {
|
|
LOG(ERROR) << "Could not read source partition metadata.\n";
|
|
return false;
|
|
}
|
|
|
|
auto dap = manifest_.mutable_dynamic_partition_metadata();
|
|
dap->set_snapshot_enabled(true);
|
|
dap->set_vabc_enabled(true);
|
|
dap->set_vabc_compression_param("lz4");
|
|
dap->set_cow_version(3);
|
|
|
|
for (const auto& entry : std::filesystem::directory_iterator(snapshot_dir_path_)) {
|
|
if (android::base::EndsWith(entry.path().generic_string(), ".patch")) {
|
|
patchfiles_.push_back(android::base::Basename(entry.path().generic_string()));
|
|
}
|
|
}
|
|
|
|
for (auto& patchfile : patchfiles_) {
|
|
std::string parsing_file = snapshot_dir_path_ + patchfile;
|
|
android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(parsing_file.c_str(), O_RDONLY)));
|
|
if (fd < 0) {
|
|
LOG(ERROR) << "Failed to open file: " << parsing_file;
|
|
return false;
|
|
}
|
|
uint64_t dev_sz = lseek(fd.get(), 0, SEEK_END);
|
|
if (!dev_sz) {
|
|
LOG(ERROR) << "Could not determine block device size: " << parsing_file;
|
|
return false;
|
|
}
|
|
|
|
const int block_sz = 4_KiB;
|
|
dev_sz += block_sz - 1;
|
|
dev_sz &= ~(block_sz - 1);
|
|
|
|
auto npos = patchfile.rfind(".patch");
|
|
auto partition_name = patchfile.substr(0, npos);
|
|
|
|
chromeos_update_engine::DynamicPartitionGroup* group = nullptr;
|
|
std::string group_name = GetGroupName(*source_metadata.get(), partition_name);
|
|
if (group_map_.find(group_name) != group_map_.end()) {
|
|
group = group_map_[group_name];
|
|
} else {
|
|
group = dap->add_groups();
|
|
group->set_name(group_name);
|
|
group_map_[group_name] = group;
|
|
}
|
|
group->add_partition_names(partition_name);
|
|
|
|
auto pu = manifest_.mutable_partitions()->Add();
|
|
pu->set_partition_name(partition_name);
|
|
pu->set_estimate_cow_size(dev_sz);
|
|
|
|
CowReader reader;
|
|
if (!reader.Parse(fd)) {
|
|
LOG(ERROR) << "COW reader parse failed";
|
|
return false;
|
|
}
|
|
|
|
uint64_t new_device_size = 0;
|
|
const auto& header = reader.GetHeader();
|
|
if (header.prefix.major_version == 2) {
|
|
size_t num_ops = reader.get_num_total_data_ops();
|
|
new_device_size = (num_ops * header.block_size);
|
|
} else {
|
|
const auto& v3_header = reader.header_v3();
|
|
new_device_size = v3_header.op_count_max * v3_header.block_size;
|
|
}
|
|
|
|
LOG(INFO) << "Partition: " << partition_name << " Group_name: " << group_name
|
|
<< " size: " << new_device_size << " COW-size: " << dev_sz;
|
|
pu->mutable_new_partition_info()->set_size(new_device_size);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool MapSnapshots::GetCowDevicePath(std::string partition_name, std::string* cow_path) {
|
|
auto& dm = android::dm::DeviceMapper::Instance();
|
|
|
|
std::string cow_device = partition_name + "-cow-img";
|
|
if (metadata_super_) {
|
|
// If COW device exists on /data, then data wipe cannot be done.
|
|
if (dm.GetDmDevicePathByName(cow_device, cow_path)) {
|
|
LOG(ERROR) << "COW device exists on /data: " << *cow_path;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
cow_device = partition_name + "-cow";
|
|
if (dm.GetDmDevicePathByName(cow_device, cow_path)) {
|
|
return true;
|
|
}
|
|
|
|
LOG(INFO) << "Failed to find cow path: " << cow_device << " Checking the device for -img path";
|
|
if (!dm.GetDmDevicePathByName(cow_device, cow_path)) {
|
|
LOG(ERROR) << "Failed to cow path: " << cow_device;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool MapSnapshots::ApplyUpdate() {
|
|
if (!PrepareUpdate()) {
|
|
LOG(ERROR) << "PrepareUpdate failed";
|
|
return false;
|
|
}
|
|
if (!sm_->BeginUpdate()) {
|
|
LOG(ERROR) << "BeginUpdate failed";
|
|
return false;
|
|
}
|
|
if (!sm_->CreateUpdateSnapshots(manifest_)) {
|
|
LOG(ERROR) << "Could not apply snapshots";
|
|
return false;
|
|
}
|
|
|
|
LOG(INFO) << "CreateUpdateSnapshots success";
|
|
if (!sm_->MapAllSnapshots(10s)) {
|
|
LOG(ERROR) << "MapAllSnapshots failed";
|
|
return false;
|
|
}
|
|
|
|
LOG(INFO) << "MapAllSnapshots success";
|
|
|
|
auto target_slot = fs_mgr_get_other_slot_suffix();
|
|
for (auto& patchfile : patchfiles_) {
|
|
auto npos = patchfile.rfind(".patch");
|
|
auto partition_name = patchfile.substr(0, npos) + target_slot;
|
|
std::string cow_path;
|
|
if (!GetCowDevicePath(partition_name, &cow_path)) {
|
|
LOG(ERROR) << "Failed to find cow path";
|
|
return false;
|
|
}
|
|
threads_.emplace_back(std::async(std::launch::async, &MapSnapshots::WriteSnapshotPatch,
|
|
this, cow_path, patchfile));
|
|
}
|
|
|
|
bool ret = true;
|
|
for (auto& t : threads_) {
|
|
ret = t.get() && ret;
|
|
}
|
|
if (!ret) {
|
|
LOG(ERROR) << "Snapshot writes failed";
|
|
return false;
|
|
}
|
|
if (!sm_->UnmapAllSnapshots()) {
|
|
LOG(ERROR) << "UnmapAllSnapshots failed";
|
|
return false;
|
|
}
|
|
|
|
LOG(INFO) << "Pre-created snapshots successfully copied";
|
|
// All snapshots have been written.
|
|
if (!sm_->FinishedSnapshotWrites(false /* wipe */)) {
|
|
LOG(ERROR) << "Could not finalize snapshot writes.\n";
|
|
return false;
|
|
}
|
|
|
|
auto hal = hal::BootControlClient::WaitForService();
|
|
if (!hal) {
|
|
LOG(ERROR) << "Could not find IBootControl HAL.\n";
|
|
return false;
|
|
}
|
|
auto target_slot_number = SlotNumberForSlotSuffix(target_slot);
|
|
auto cr = hal->SetActiveBootSlot(target_slot_number);
|
|
if (!cr.IsOk()) {
|
|
LOG(ERROR) << "Could not set active boot slot: " << cr.errMsg;
|
|
return false;
|
|
}
|
|
|
|
LOG(INFO) << "ApplyUpdate success";
|
|
return true;
|
|
}
|
|
|
|
bool MapSnapshots::BeginUpdate() {
|
|
if (metadata_super_ && !CreateScratchOtaMetadataOnSuper()) {
|
|
LOG(ERROR) << "Failed to create OTA metadata on super";
|
|
return false;
|
|
}
|
|
sm_ = SnapshotManager::New();
|
|
|
|
lock_ = sm_->LockExclusive();
|
|
std::vector<std::string> snapshots;
|
|
sm_->ListSnapshots(lock_.get(), &snapshots);
|
|
if (!snapshots.empty()) {
|
|
// Snapshots are already present.
|
|
return true;
|
|
}
|
|
|
|
lock_ = nullptr;
|
|
if (!sm_->BeginUpdate()) {
|
|
LOG(ERROR) << "BeginUpdate failed";
|
|
return false;
|
|
}
|
|
lock_ = sm_->LockExclusive();
|
|
return true;
|
|
}
|
|
|
|
bool MapSnapshots::CreateSnapshotDevice(std::string& partition_name, std::string& patchfile) {
|
|
std::string parsing_file = snapshot_dir_path_ + patchfile;
|
|
|
|
android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(parsing_file.c_str(), O_RDONLY)));
|
|
if (fd < 0) {
|
|
LOG(ERROR) << "Failed to open file: " << parsing_file;
|
|
return false;
|
|
}
|
|
|
|
uint64_t dev_sz = lseek(fd.get(), 0, SEEK_END);
|
|
if (!dev_sz) {
|
|
LOG(ERROR) << "Could not determine block device size: " << parsing_file;
|
|
return false;
|
|
}
|
|
|
|
const int block_sz = 4_KiB;
|
|
dev_sz += block_sz - 1;
|
|
dev_sz &= ~(block_sz - 1);
|
|
|
|
SnapshotStatus status;
|
|
status.set_state(SnapshotState::CREATED);
|
|
status.set_using_snapuserd(true);
|
|
status.set_old_partition_size(0);
|
|
status.set_name(partition_name);
|
|
status.set_cow_file_size(dev_sz);
|
|
status.set_cow_partition_size(0);
|
|
|
|
PartitionCowCreator cow_creator;
|
|
cow_creator.using_snapuserd = true;
|
|
|
|
if (!sm_->CreateSnapshot(lock_.get(), &cow_creator, &status)) {
|
|
LOG(ERROR) << "CreateSnapshot failed";
|
|
return false;
|
|
}
|
|
|
|
if (!sm_->CreateCowImage(lock_.get(), partition_name)) {
|
|
LOG(ERROR) << "CreateCowImage failed";
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
std::optional<std::string> MapSnapshots::GetCowImagePath(std::string& name) {
|
|
auto cow_dev = sm_->MapCowImage(name, 5s);
|
|
if (!cow_dev.has_value()) {
|
|
LOG(ERROR) << "Failed to get COW device path";
|
|
return std::nullopt;
|
|
}
|
|
|
|
LOG(INFO) << "COW Device path: " << cow_dev.value();
|
|
return cow_dev;
|
|
}
|
|
|
|
bool MapSnapshots::WriteSnapshotPatch(std::string cow_device, std::string patch) {
|
|
std::string patch_file = snapshot_dir_path_ + patch;
|
|
|
|
android::base::unique_fd fd(TEMP_FAILURE_RETRY(open(patch_file.c_str(), O_RDONLY)));
|
|
if (fd < 0) {
|
|
LOG(ERROR) << "Failed to open file: " << patch_file;
|
|
return false;
|
|
}
|
|
|
|
uint64_t dev_sz = lseek(fd.get(), 0, SEEK_END);
|
|
if (!dev_sz) {
|
|
std::cout << "Could not determine block device size: " << patch_file;
|
|
return false;
|
|
}
|
|
|
|
android::base::unique_fd cfd(TEMP_FAILURE_RETRY(open(cow_device.c_str(), O_RDWR)));
|
|
if (cfd < 0) {
|
|
LOG(ERROR) << "Failed to open file: " << cow_device;
|
|
return false;
|
|
}
|
|
|
|
const uint64_t read_sz = 1_MiB;
|
|
std::unique_ptr<uint8_t[]> buffer = std::make_unique<uint8_t[]>(read_sz);
|
|
off_t file_offset = 0;
|
|
|
|
while (true) {
|
|
size_t to_read = std::min((dev_sz - file_offset), read_sz);
|
|
if (!android::base::ReadFullyAtOffset(fd.get(), buffer.get(), to_read, file_offset)) {
|
|
PLOG(ERROR) << "ReadFullyAtOffset failed";
|
|
return false;
|
|
}
|
|
|
|
if (!android::base::WriteFullyAtOffset(cfd, buffer.get(), to_read, file_offset)) {
|
|
PLOG(ERROR) << "WriteFullyAtOffset failed";
|
|
return false;
|
|
}
|
|
file_offset += to_read;
|
|
if (file_offset >= dev_sz) {
|
|
break;
|
|
}
|
|
}
|
|
if (fsync(cfd.get()) < 0) {
|
|
PLOG(ERROR) << "Fsync failed";
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool MapSnapshots::InitiateThreadedSnapshotWrite(std::string& pname, std::string& snapshot_patch) {
|
|
auto path = GetCowImagePath(pname);
|
|
if (!path.has_value()) {
|
|
return false;
|
|
}
|
|
threads_.emplace_back(std::async(std::launch::async, &MapSnapshots::WriteSnapshotPatch, this,
|
|
path.value(), snapshot_patch));
|
|
return true;
|
|
}
|
|
|
|
bool MapSnapshots::FinishSnapshotWrites() {
|
|
bool ret = true;
|
|
for (auto& t : threads_) {
|
|
ret = t.get() && ret;
|
|
}
|
|
|
|
lock_ = nullptr;
|
|
if (ret) {
|
|
LOG(INFO) << "Pre-created snapshots successfully copied";
|
|
if (!sm_->FinishedSnapshotWrites(false)) {
|
|
return false;
|
|
}
|
|
return sm_->BootFromSnapshotsWithoutSlotSwitch();
|
|
}
|
|
|
|
LOG(ERROR) << "Snapshot copy failed";
|
|
return false;
|
|
}
|
|
|
|
bool MapSnapshots::UnmapCowImagePath(std::string& name) {
|
|
sm_ = SnapshotManager::New();
|
|
return sm_->UnmapCowImage(name);
|
|
}
|
|
|
|
bool MapSnapshots::DeleteSnapshots() {
|
|
sm_ = SnapshotManager::New();
|
|
lock_ = sm_->LockExclusive();
|
|
if (!sm_->RemoveAllUpdateState(lock_.get())) {
|
|
LOG(ERROR) << "Remove All Update State failed";
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
bool DumpCmdHandler(int /*argc*/, char** argv) {
|
|
android::base::InitLogging(argv, TeeLogger(LogdLogger(), &StderrLogger));
|
|
return SnapshotManager::New()->Dump(std::cout);
|
|
}
|
|
|
|
bool MapCmdHandler(int, char** argv) {
|
|
android::base::InitLogging(argv, TeeLogger(LogdLogger(), &StderrLogger));
|
|
using namespace std::chrono_literals;
|
|
return SnapshotManager::New()->MapAllSnapshots(5000ms);
|
|
}
|
|
|
|
bool UnmapCmdHandler(int, char** argv) {
|
|
android::base::InitLogging(argv, TeeLogger(LogdLogger(), &StderrLogger));
|
|
return SnapshotManager::New()->UnmapAllSnapshots();
|
|
}
|
|
|
|
bool MergeCmdHandler(int /*argc*/, char** argv) {
|
|
android::base::InitLogging(argv, TeeLogger(LogdLogger(), &StderrLogger));
|
|
LOG(WARNING) << "Deprecated. Call update_engine_client --merge instead.";
|
|
return false;
|
|
}
|
|
|
|
#ifdef SNAPSHOTCTL_USERDEBUG_OR_ENG
|
|
bool GetVerityPartitions(std::vector<std::string>& partitions) {
|
|
auto& dm = android::dm::DeviceMapper::Instance();
|
|
auto dm_block_devices = dm.FindDmPartitions();
|
|
if (dm_block_devices.empty()) {
|
|
LOG(ERROR) << "No dm-enabled block device is found.";
|
|
return false;
|
|
}
|
|
|
|
for (auto& block_device : dm_block_devices) {
|
|
std::string dm_block_name = block_device.first;
|
|
std::string slot_suffix = fs_mgr_get_slot_suffix();
|
|
std::string partition = dm_block_name + slot_suffix;
|
|
partitions.push_back(partition);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool UnMapPrecreatedSnapshots(int, char** argv) {
|
|
android::base::InitLogging(argv, &android::base::KernelLogger);
|
|
// Make sure we are root.
|
|
if (::getuid() != 0) {
|
|
LOG(ERROR) << "Not running as root. Try \"adb root\" first.";
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
std::vector<std::string> partitions;
|
|
if (!GetVerityPartitions(partitions)) {
|
|
return false;
|
|
}
|
|
|
|
MapSnapshots snapshot;
|
|
for (auto partition : partitions) {
|
|
if (!snapshot.UnmapCowImagePath(partition)) {
|
|
LOG(ERROR) << "UnmapCowImagePath failed: " << partition;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool RemovePrecreatedSnapshots(int, char** argv) {
|
|
android::base::InitLogging(argv, &android::base::KernelLogger);
|
|
// Make sure we are root.
|
|
if (::getuid() != 0) {
|
|
LOG(ERROR) << "Not running as root. Try \"adb root\" first.";
|
|
return false;
|
|
}
|
|
|
|
MapSnapshots snapshot;
|
|
if (!snapshot.CleanupSnapshot()) {
|
|
LOG(ERROR) << "CleanupSnapshot failed";
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool DeletePrecreatedSnapshots(int, char** argv) {
|
|
android::base::InitLogging(argv, &android::base::KernelLogger);
|
|
// Make sure we are root.
|
|
if (::getuid() != 0) {
|
|
LOG(ERROR) << "Not running as root. Try \"adb root\" first.";
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
MapSnapshots snapshot;
|
|
return snapshot.DeleteSnapshots();
|
|
}
|
|
|
|
bool ApplyUpdate(int argc, char** argv) {
|
|
android::base::InitLogging(argv, &android::base::KernelLogger);
|
|
|
|
// Make sure we are root.
|
|
if (::getuid() != 0) {
|
|
LOG(ERROR) << "Not running as root. Try \"adb root\" first.";
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
if (argc < 3) {
|
|
std::cerr << " apply-update <directory location where snapshot patches are present> {-w}"
|
|
" Apply the snapshots to the COW block device\n";
|
|
return false;
|
|
}
|
|
|
|
std::string path = std::string(argv[2]);
|
|
bool metadata_on_super = false;
|
|
if (argc == 4) {
|
|
if (std::string(argv[3]) == "-w") {
|
|
metadata_on_super = true;
|
|
}
|
|
}
|
|
MapSnapshots cow(path, metadata_on_super);
|
|
if (!cow.ApplyUpdate()) {
|
|
return false;
|
|
}
|
|
LOG(INFO) << "Apply update success. Please reboot the device";
|
|
return true;
|
|
}
|
|
|
|
bool MapPrecreatedSnapshots(int argc, char** argv) {
|
|
android::base::InitLogging(argv, &android::base::KernelLogger);
|
|
|
|
// Make sure we are root.
|
|
if (::getuid() != 0) {
|
|
LOG(ERROR) << "Not running as root. Try \"adb root\" first.";
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
if (argc < 3) {
|
|
std::cerr << " map-snapshots <directory location where snapshot patches are present> {-w}"
|
|
" Map all snapshots based on patches present in the directory\n";
|
|
return false;
|
|
}
|
|
|
|
std::string path = std::string(argv[2]);
|
|
std::vector<std::string> patchfiles;
|
|
|
|
for (const auto& entry : std::filesystem::directory_iterator(path)) {
|
|
if (android::base::EndsWith(entry.path().generic_string(), ".patch")) {
|
|
patchfiles.push_back(android::base::Basename(entry.path().generic_string()));
|
|
}
|
|
}
|
|
auto& dm = android::dm::DeviceMapper::Instance();
|
|
auto dm_block_devices = dm.FindDmPartitions();
|
|
if (dm_block_devices.empty()) {
|
|
LOG(ERROR) << "No dm-enabled block device is found.";
|
|
return false;
|
|
}
|
|
|
|
std::vector<std::pair<std::string, std::string>> partitions;
|
|
for (auto& patchfile : patchfiles) {
|
|
auto npos = patchfile.rfind(".patch");
|
|
auto dm_block_name = patchfile.substr(0, npos);
|
|
if (dm_block_devices.find(dm_block_name) != dm_block_devices.end()) {
|
|
std::string slot_suffix = fs_mgr_get_slot_suffix();
|
|
std::string partition = dm_block_name + slot_suffix;
|
|
partitions.push_back(std::make_pair(partition, patchfile));
|
|
}
|
|
}
|
|
|
|
bool metadata_on_super = false;
|
|
if (argc == 4) {
|
|
if (std::string(argv[3]) == "-w") {
|
|
metadata_on_super = true;
|
|
}
|
|
}
|
|
|
|
MapSnapshots cow(path, metadata_on_super);
|
|
if (!cow.BeginUpdate()) {
|
|
LOG(ERROR) << "BeginUpdate failed";
|
|
return false;
|
|
}
|
|
|
|
for (auto& pair : partitions) {
|
|
if (!cow.CreateSnapshotDevice(pair.first, pair.second)) {
|
|
LOG(ERROR) << "CreateSnapshotDevice failed for: " << pair.first;
|
|
return false;
|
|
}
|
|
if (!cow.InitiateThreadedSnapshotWrite(pair.first, pair.second)) {
|
|
LOG(ERROR) << "InitiateThreadedSnapshotWrite failed for: " << pair.first;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return cow.FinishSnapshotWrites();
|
|
}
|
|
|
|
bool CreateTestUpdate(SnapshotManager* sm) {
|
|
chromeos_update_engine::DeltaArchiveManifest manifest;
|
|
|
|
// We only copy system, to simplify things.
|
|
manifest.set_partial_update(true);
|
|
|
|
auto dap = manifest.mutable_dynamic_partition_metadata();
|
|
dap->set_snapshot_enabled(true);
|
|
dap->set_vabc_enabled(true);
|
|
dap->set_vabc_compression_param("none");
|
|
dap->set_cow_version(kCowVersionMajor);
|
|
|
|
auto source_slot = fs_mgr_get_slot_suffix();
|
|
auto source_slot_number = SlotNumberForSlotSuffix(source_slot);
|
|
auto target_slot = fs_mgr_get_other_slot_suffix();
|
|
auto target_slot_number = SlotNumberForSlotSuffix(target_slot);
|
|
auto super_source = fs_mgr_get_super_partition_name(source_slot_number);
|
|
|
|
// Get current partition information.
|
|
PartitionOpener opener;
|
|
auto source_metadata = ReadMetadata(opener, super_source, source_slot_number);
|
|
if (!source_metadata) {
|
|
std::cerr << "Could not read source partition metadata.\n";
|
|
return false;
|
|
}
|
|
|
|
auto system_source_name = "system" + source_slot;
|
|
auto system_source = FindPartition(*source_metadata.get(), system_source_name);
|
|
if (!system_source) {
|
|
std::cerr << "Could not find system partition: " << system_source_name << ".\n";
|
|
return false;
|
|
}
|
|
auto system_source_size = GetPartitionSize(*source_metadata.get(), *system_source);
|
|
|
|
// Since we only add copy operations, 64MB should be enough.
|
|
auto system_update = manifest.mutable_partitions()->Add();
|
|
system_update->set_partition_name("system");
|
|
system_update->set_estimate_cow_size(64_MiB);
|
|
system_update->mutable_new_partition_info()->set_size(system_source_size);
|
|
|
|
if (!sm->CreateUpdateSnapshots(manifest)) {
|
|
std::cerr << "Could not create update snapshots.\n";
|
|
return false;
|
|
}
|
|
|
|
// Write the "new" system partition.
|
|
auto system_target_name = "system" + target_slot;
|
|
CreateLogicalPartitionParams clpp = {
|
|
.block_device = fs_mgr_get_super_partition_name(target_slot_number),
|
|
.metadata_slot = {target_slot_number},
|
|
.partition_name = system_target_name,
|
|
.timeout_ms = 10s,
|
|
.partition_opener = &opener,
|
|
};
|
|
auto writer = sm->OpenSnapshotWriter(clpp, std::nullopt);
|
|
if (!writer) {
|
|
std::cerr << "Could not open snapshot writer.\n";
|
|
return false;
|
|
}
|
|
|
|
for (uint64_t block = 0; block < system_source_size / 4096; block++) {
|
|
if (!writer->AddCopy(block, block)) {
|
|
std::cerr << "Unable to add copy operation for block " << block << ".\n";
|
|
return false;
|
|
}
|
|
}
|
|
if (!writer->Finalize()) {
|
|
std::cerr << "Could not finalize COW for " << system_target_name << ".\n";
|
|
return false;
|
|
}
|
|
writer = nullptr;
|
|
|
|
// Finished writing this partition, unmap.
|
|
if (!sm->UnmapUpdateSnapshot(system_target_name)) {
|
|
std::cerr << "Could not unmap snapshot for " << system_target_name << ".\n";
|
|
return false;
|
|
}
|
|
|
|
// All snapshots have been written.
|
|
if (!sm->FinishedSnapshotWrites(false /* wipe */)) {
|
|
std::cerr << "Could not finalize snapshot writes.\n";
|
|
return false;
|
|
}
|
|
|
|
auto hal = hal::BootControlClient::WaitForService();
|
|
if (!hal) {
|
|
std::cerr << "Could not find IBootControl HAL.\n";
|
|
return false;
|
|
}
|
|
auto cr = hal->SetActiveBootSlot(target_slot_number);
|
|
if (!cr.IsOk()) {
|
|
std::cerr << "Could not set active boot slot: " << cr.errMsg;
|
|
return false;
|
|
}
|
|
|
|
std::cerr << "It is now safe to reboot your device. If using a physical device, make\n"
|
|
<< "sure that all physical partitions are flashed to both A and B slots.\n";
|
|
return true;
|
|
}
|
|
|
|
bool TestOtaHandler(int /* argc */, char** /* argv */) {
|
|
auto sm = SnapshotManager::New();
|
|
|
|
if (!sm->BeginUpdate()) {
|
|
std::cerr << "Error starting update.\n";
|
|
return false;
|
|
}
|
|
|
|
if (!CreateTestUpdate(sm.get())) {
|
|
sm->CancelUpdate();
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
#endif
|
|
|
|
static std::map<std::string, std::function<bool(int, char**)>> kCmdMap = {
|
|
// clang-format off
|
|
{"dump", DumpCmdHandler},
|
|
{"merge", MergeCmdHandler},
|
|
{"map", MapCmdHandler},
|
|
#ifdef SNAPSHOTCTL_USERDEBUG_OR_ENG
|
|
{"test-blank-ota", TestOtaHandler},
|
|
{"apply-update", ApplyUpdate},
|
|
{"map-snapshots", MapPrecreatedSnapshots},
|
|
{"unmap-snapshots", UnMapPrecreatedSnapshots},
|
|
{"delete-snapshots", DeletePrecreatedSnapshots},
|
|
{"revert-snapshots", RemovePrecreatedSnapshots},
|
|
#endif
|
|
{"unmap", UnmapCmdHandler},
|
|
// clang-format on
|
|
};
|
|
|
|
} // namespace snapshot
|
|
} // namespace android
|
|
|
|
int main(int argc, char** argv) {
|
|
using namespace android::snapshot;
|
|
if (argc < 2) {
|
|
return Usage();
|
|
}
|
|
|
|
for (const auto& cmd : kCmdMap) {
|
|
if (cmd.first == argv[1]) {
|
|
return cmd.second(argc, argv) ? EX_OK : EX_SOFTWARE;
|
|
}
|
|
}
|
|
|
|
return Usage();
|
|
}
|