blob: e1a3310c30eff8fb28d55318b1d0a977a498f293 [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 <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/hex.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 <android/snapshot/snapshot.pb.h>
#include <fs_avb/fs_avb_util.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 <openssl/sha.h>
#include "partition_cow_creator.h"
#include "scratch_super.h"
#include "utility.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"
" dump-verity-hash <directory where verity merkel tree hashes are stored> "
"[-verify]\n"
" Dump the verity merkel tree hashes at the specified path\n"
" -verify: Verify the dynamic partition blocks by comparing it with verity "
"merkel tree\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();
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 the COW device exists only on /data
cow_device = partition_name + "-cow-img";
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::CleanupSnapshot() {
sm_ = SnapshotManager::New();
return sm_->PrepareDeviceToBootWithoutSnapshot();
}
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;
}
static bool GetBlockHashFromMerkelTree(android::base::borrowed_fd image_fd, uint64_t image_size,
uint32_t data_block_size, uint32_t hash_block_size,
uint64_t tree_offset,
std::vector<std::string>& out_block_hash) {
uint32_t padded_digest_size = 32;
if (image_size % data_block_size != 0) {
LOG(ERROR) << "Image_size: " << image_size
<< " not a multiple of data block size: " << data_block_size;
return false;
}
// vector of level-size and offset
std::vector<std::pair<uint64_t, uint64_t>> levels;
uint64_t data_block_count = image_size / data_block_size;
uint32_t digests_per_block = hash_block_size / padded_digest_size;
uint32_t level_block_count = data_block_count;
while (level_block_count > 1) {
uint32_t next_level_block_count =
(level_block_count + digests_per_block - 1) / digests_per_block;
levels.emplace_back(std::make_pair(next_level_block_count * hash_block_size, 0));
level_block_count = next_level_block_count;
}
// root digest
levels.emplace_back(std::make_pair(0, 0));
// initialize offset
for (auto level = std::prev(levels.end()); level != levels.begin(); level--) {
std::prev(level)->second = level->second + level->first;
}
// We just want level 0
auto level = levels.begin();
std::string hash_block(hash_block_size, '\0');
uint64_t block_offset = tree_offset + level->second;
uint64_t t_read_blocks = 0;
uint64_t blockidx = 0;
uint64_t num_hash_blocks = level->first / hash_block_size;
while ((t_read_blocks < num_hash_blocks) && (blockidx < data_block_count)) {
if (!android::base::ReadFullyAtOffset(image_fd, hash_block.data(), hash_block.size(),
block_offset)) {
LOG(ERROR) << "Failed to read tree block at offset: " << block_offset;
return false;
}
for (uint32_t offset = 0; offset < hash_block.size(); offset += padded_digest_size) {
std::string single_hash = hash_block.substr(offset, padded_digest_size);
out_block_hash.emplace_back(single_hash);
blockidx += 1;
if (blockidx >= data_block_count) {
break;
}
}
block_offset += hash_block_size;
t_read_blocks += 1;
}
return true;
}
static bool CalculateDigest(const void* buffer, size_t size, const void* salt, uint32_t salt_length,
uint8_t* digest) {
SHA256_CTX ctx;
if (SHA256_Init(&ctx) != 1) {
return false;
}
if (SHA256_Update(&ctx, salt, salt_length) != 1) {
return false;
}
if (SHA256_Update(&ctx, buffer, size) != 1) {
return false;
}
if (SHA256_Final(digest, &ctx) != 1) {
return false;
}
return true;
}
bool verify_data_blocks(android::base::borrowed_fd fd, const std::vector<std::string>& block_hash,
std::unique_ptr<android::fs_mgr::FsAvbHashtreeDescriptor>& descriptor,
const std::vector<uint8_t>& salt) {
uint64_t data_block_count = descriptor->image_size / descriptor->data_block_size;
uint64_t foffset = 0;
uint64_t blk = 0;
std::string hash_block(descriptor->hash_block_size, '\0');
while (blk < data_block_count) {
if (!android::base::ReadFullyAtOffset(fd, hash_block.data(), descriptor->hash_block_size,
foffset)) {
LOG(ERROR) << "Failed to read from offset: " << foffset;
return false;
}
std::string digest(32, '\0');
CalculateDigest(hash_block.data(), descriptor->hash_block_size, salt.data(), salt.size(),
reinterpret_cast<uint8_t*>(digest.data()));
if (digest != block_hash[blk]) {
LOG(ERROR) << "Hash mismatch for block: " << blk << " Expected: " << block_hash[blk]
<< " Received: " << digest;
return false;
}
foffset += descriptor->hash_block_size;
blk += 1;
}
return true;
}
bool DumpVerityHash(int argc, char** argv) {
android::base::InitLogging(argv, &android::base::KernelLogger);
if (::getuid() != 0) {
LOG(ERROR) << "Not running as root. Try \"adb root\" first.";
return EXIT_FAILURE;
}
if (argc < 3) {
std::cerr
<< " dump-verity-hash <directory location where verity hash is saved> {-verify}\n";
return false;
}
bool verification_required = false;
std::string hash_file_path = argv[2];
bool metadata_on_super = false;
if (argc == 4) {
if (argv[3] == "-verify"s) {
verification_required = true;
}
}
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;
}
android::fs_mgr::Fstab fstab;
if (!ReadDefaultFstab(&fstab)) {
LOG(ERROR) << "Failed to read fstab";
return false;
}
for (const auto& pair : dm_block_devices) {
std::string partition_name = pair.first;
android::fs_mgr::FstabEntry* fstab_entry =
GetEntryForMountPoint(&fstab, "/" + partition_name);
auto vbmeta = LoadAndVerifyVbmeta(*fstab_entry, "", nullptr, nullptr, nullptr);
if (vbmeta == nullptr) {
LOG(ERROR) << "LoadAndVerifyVbmetaByPath failed for partition: " << partition_name;
return false;
}
auto descriptor =
android::fs_mgr::GetHashtreeDescriptor(partition_name, std::move(*vbmeta));
if (descriptor == nullptr) {
LOG(ERROR) << "GetHashtreeDescriptor failed for partition: " << partition_name;
return false;
}
std::string device_path = fstab_entry->blk_device;
if (!dm.GetDmDevicePathByName(fstab_entry->blk_device, &device_path)) {
LOG(ERROR) << "Failed to resolve logical device path for: " << fstab_entry->blk_device;
return false;
}
android::base::unique_fd fd(open(device_path.c_str(), O_RDONLY));
if (fd < 0) {
LOG(ERROR) << "Failed to open file: " << device_path;
return false;
}
std::vector<std::string> block_hash;
if (!GetBlockHashFromMerkelTree(fd, descriptor->image_size, descriptor->data_block_size,
descriptor->hash_block_size, descriptor->tree_offset,
block_hash)) {
LOG(ERROR) << "GetBlockHashFromMerkelTree failed";
return false;
}
uint64_t dev_sz = lseek(fd, 0, SEEK_END);
uint64_t fec_size = dev_sz - descriptor->image_size;
if (fec_size % descriptor->data_block_size != 0) {
LOG(ERROR) << "fec_size: " << fec_size
<< " isn't multiple of: " << descriptor->data_block_size;
return false;
}
std::vector<uint8_t> salt;
const std::string& salt_str = descriptor->salt;
bool ok = android::base::HexToBytes(salt_str, &salt);
if (!ok) {
LOG(ERROR) << "HexToBytes conversion failed";
return false;
}
uint64_t file_offset = descriptor->image_size;
std::vector<uint8_t> hash_block(descriptor->hash_block_size, 0);
while (file_offset < dev_sz) {
if (!android::base::ReadFullyAtOffset(fd, hash_block.data(),
descriptor->hash_block_size, file_offset)) {
LOG(ERROR) << "Failed to read tree block at offset: " << file_offset;
return false;
}
std::string digest(32, '\0');
CalculateDigest(hash_block.data(), descriptor->hash_block_size, salt.data(),
salt.size(), reinterpret_cast<uint8_t*>(digest.data()));
block_hash.push_back(digest);
file_offset += descriptor->hash_block_size;
fec_size -= descriptor->hash_block_size;
}
if (fec_size != 0) {
LOG(ERROR) << "Checksum calculation pending: " << fec_size;
return false;
}
if (verification_required) {
if (!verify_data_blocks(fd, block_hash, descriptor, salt)) {
LOG(ERROR) << "verify_data_blocks failed";
return false;
}
}
VerityHash verity_hash;
verity_hash.set_partition_name(partition_name);
verity_hash.set_salt(salt_str);
for (auto hash : block_hash) {
verity_hash.add_block_hash(hash.data(), hash.size());
}
std::string hash_file = hash_file_path + "/" + partition_name + ".pb";
std::string content;
if (!verity_hash.SerializeToString(&content)) {
LOG(ERROR) << "Unable to serialize verity_hash";
return false;
}
if (!WriteStringToFileAtomic(content, hash_file)) {
PLOG(ERROR) << "Unable to write VerityHash to " << hash_file;
return false;
}
LOG(INFO) << partition_name
<< ": GetBlockHashFromMerkelTree success. Num Blocks: " << block_hash.size();
}
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},
{"dump-verity-hash", DumpVerityHash},
#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();
}