915 lines
32 KiB
C++
915 lines
32 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 <libfiemap/image_manager.h>
|
||
|
|
||
|
#include <optional>
|
||
|
|
||
|
#include <android-base/file.h>
|
||
|
#include <android-base/logging.h>
|
||
|
#include <android-base/properties.h>
|
||
|
#include <android-base/strings.h>
|
||
|
#include <android-base/unique_fd.h>
|
||
|
#include <ext4_utils/ext4_utils.h>
|
||
|
#include <fs_mgr/file_wait.h>
|
||
|
#include <fs_mgr_dm_linear.h>
|
||
|
#include <libdm/loop_control.h>
|
||
|
#include <libfiemap/split_fiemap_writer.h>
|
||
|
#include <libgsi/libgsi.h>
|
||
|
|
||
|
#include "metadata.h"
|
||
|
#include "utility.h"
|
||
|
|
||
|
namespace android {
|
||
|
namespace fiemap {
|
||
|
|
||
|
using namespace std::literals;
|
||
|
using android::base::ReadFileToString;
|
||
|
using android::base::unique_fd;
|
||
|
using android::dm::DeviceMapper;
|
||
|
using android::dm::DmDeviceState;
|
||
|
using android::dm::DmTable;
|
||
|
using android::dm::DmTargetLinear;
|
||
|
using android::dm::LoopControl;
|
||
|
using android::fs_mgr::CreateLogicalPartition;
|
||
|
using android::fs_mgr::CreateLogicalPartitionParams;
|
||
|
using android::fs_mgr::CreateLogicalPartitions;
|
||
|
using android::fs_mgr::DestroyLogicalPartition;
|
||
|
using android::fs_mgr::GetBlockDevicePartitionName;
|
||
|
using android::fs_mgr::GetBlockDevicePartitionNames;
|
||
|
using android::fs_mgr::GetPartitionName;
|
||
|
|
||
|
static constexpr char kTestImageMetadataDir[] = "/metadata/gsi/test";
|
||
|
static constexpr char kOtaTestImageMetadataDir[] = "/metadata/gsi/ota/test";
|
||
|
|
||
|
std::unique_ptr<ImageManager> ImageManager::Open(const std::string& dir_prefix,
|
||
|
const DeviceInfo& device_info) {
|
||
|
auto metadata_dir = "/metadata/gsi/" + dir_prefix;
|
||
|
auto data_dir = "/data/gsi/" + dir_prefix;
|
||
|
auto install_dir_file = gsi::DsuInstallDirFile(gsi::GetDsuSlot(dir_prefix));
|
||
|
std::string path;
|
||
|
if (ReadFileToString(install_dir_file, &path)) {
|
||
|
data_dir = path;
|
||
|
}
|
||
|
return Open(metadata_dir, data_dir, device_info);
|
||
|
}
|
||
|
|
||
|
std::unique_ptr<ImageManager> ImageManager::Open(const std::string& metadata_dir,
|
||
|
const std::string& data_dir,
|
||
|
const DeviceInfo& device_info) {
|
||
|
return std::unique_ptr<ImageManager>(new ImageManager(metadata_dir, data_dir, device_info));
|
||
|
}
|
||
|
|
||
|
ImageManager::ImageManager(const std::string& metadata_dir, const std::string& data_dir,
|
||
|
const DeviceInfo& device_info)
|
||
|
: metadata_dir_(metadata_dir), data_dir_(data_dir), device_info_(device_info) {
|
||
|
partition_opener_ = std::make_unique<android::fs_mgr::PartitionOpener>();
|
||
|
|
||
|
// Allow overriding whether ImageManager thinks it's in recovery, for testing.
|
||
|
#ifdef __ANDROID_RAMDISK__
|
||
|
device_info_.is_recovery = {true};
|
||
|
#else
|
||
|
if (!device_info_.is_recovery.has_value()) {
|
||
|
device_info_.is_recovery = {false};
|
||
|
}
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
std::string ImageManager::GetImageHeaderPath(const std::string& name) {
|
||
|
return JoinPaths(data_dir_, name) + ".img";
|
||
|
}
|
||
|
|
||
|
// The status file has one entry per line, with each entry formatted as one of:
|
||
|
// dm:<name>
|
||
|
// loop:<path>
|
||
|
//
|
||
|
// This simplifies the process of tearing down a mapping, since we can simply
|
||
|
// unmap each entry in the order it appears.
|
||
|
std::string ImageManager::GetStatusFilePath(const std::string& image_name) {
|
||
|
return JoinPaths(metadata_dir_, image_name) + ".status";
|
||
|
}
|
||
|
|
||
|
static std::string GetStatusPropertyName(const std::string& image_name) {
|
||
|
// Note: we don't prefix |image_name|, because CreateLogicalPartition won't
|
||
|
// prefix the name either. There are no plans to change this at the moment,
|
||
|
// consumers of the image API must take care to use globally-unique image
|
||
|
// names.
|
||
|
return "gsid.mapped_image." + image_name;
|
||
|
}
|
||
|
|
||
|
void ImageManager::set_partition_opener(std::unique_ptr<IPartitionOpener>&& opener) {
|
||
|
partition_opener_ = std::move(opener);
|
||
|
}
|
||
|
|
||
|
bool ImageManager::IsImageMapped(const std::string& image_name) {
|
||
|
auto prop_name = GetStatusPropertyName(image_name);
|
||
|
if (android::base::GetProperty(prop_name, "").empty()) {
|
||
|
// If mapped in first-stage init, the dm-device will exist but not the
|
||
|
// property.
|
||
|
auto& dm = DeviceMapper::Instance();
|
||
|
return dm.GetState(image_name) != DmDeviceState::INVALID;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
std::vector<std::string> ImageManager::GetAllBackingImages() {
|
||
|
std::vector<std::string> images;
|
||
|
if (!MetadataExists(metadata_dir_)) {
|
||
|
return images;
|
||
|
}
|
||
|
auto metadata = OpenMetadata(metadata_dir_);
|
||
|
if (metadata) {
|
||
|
for (auto&& partition : metadata->partitions) {
|
||
|
images.push_back(partition.name);
|
||
|
}
|
||
|
}
|
||
|
return images;
|
||
|
}
|
||
|
|
||
|
bool ImageManager::BackingImageExists(const std::string& name) {
|
||
|
if (!MetadataExists(metadata_dir_)) {
|
||
|
return false;
|
||
|
}
|
||
|
auto metadata = OpenMetadata(metadata_dir_);
|
||
|
if (!metadata) {
|
||
|
return false;
|
||
|
}
|
||
|
return !!FindPartition(*metadata.get(), name);
|
||
|
}
|
||
|
|
||
|
bool ImageManager::MetadataDirIsTest() const {
|
||
|
return IsSubdir(metadata_dir_, kTestImageMetadataDir) ||
|
||
|
IsSubdir(metadata_dir_, kOtaTestImageMetadataDir);
|
||
|
}
|
||
|
|
||
|
bool ImageManager::IsUnreliablePinningAllowed() const {
|
||
|
return IsSubdir(data_dir_, "/data/gsi/dsu/") || MetadataDirIsTest();
|
||
|
}
|
||
|
|
||
|
FiemapStatus ImageManager::CreateBackingImage(
|
||
|
const std::string& name, uint64_t size, int flags,
|
||
|
std::function<bool(uint64_t, uint64_t)>&& on_progress) {
|
||
|
auto data_path = GetImageHeaderPath(name);
|
||
|
std::unique_ptr<SplitFiemap> fw;
|
||
|
auto status = SplitFiemap::Create(data_path, size, 0, &fw, on_progress);
|
||
|
if (!status.is_ok()) {
|
||
|
return status;
|
||
|
}
|
||
|
|
||
|
bool reliable_pinning;
|
||
|
if (!FilesystemHasReliablePinning(data_path, &reliable_pinning)) {
|
||
|
return FiemapStatus::Error();
|
||
|
}
|
||
|
if (!reliable_pinning && !IsUnreliablePinningAllowed()) {
|
||
|
// For historical reasons, we allow unreliable pinning for certain use
|
||
|
// cases (DSUs, testing) because the ultimate use case is either
|
||
|
// developer-oriented or ephemeral (the intent is to boot immediately
|
||
|
// into DSUs). For everything else - such as snapshots/OTAs or adb
|
||
|
// remount, we have a higher bar, and require the filesystem to support
|
||
|
// proper pinning.
|
||
|
LOG(ERROR) << "File system does not have reliable block pinning";
|
||
|
SplitFiemap::RemoveSplitFiles(data_path);
|
||
|
return FiemapStatus::Error();
|
||
|
}
|
||
|
|
||
|
// Except for testing, we do not allow persisting metadata that references
|
||
|
// device-mapper devices. It just doesn't make sense, because the device
|
||
|
// numbering may change on reboot. We allow it for testing since the images
|
||
|
// are not meant to survive reboot. Outside of tests, this can only happen
|
||
|
// if device-mapper is stacked in some complex way not supported by
|
||
|
// FiemapWriter.
|
||
|
auto device_path = GetDevicePathForFile(fw.get());
|
||
|
if (android::base::StartsWith(device_path, "/dev/block/dm-") && !MetadataDirIsTest()) {
|
||
|
LOG(ERROR) << "Cannot persist images against device-mapper device: " << device_path;
|
||
|
|
||
|
fw = {};
|
||
|
SplitFiemap::RemoveSplitFiles(data_path);
|
||
|
return FiemapStatus::Error();
|
||
|
}
|
||
|
|
||
|
bool readonly = !!(flags & CREATE_IMAGE_READONLY);
|
||
|
if (!UpdateMetadata(metadata_dir_, name, fw.get(), size, readonly)) {
|
||
|
return FiemapStatus::Error();
|
||
|
}
|
||
|
|
||
|
if (flags & CREATE_IMAGE_ZERO_FILL) {
|
||
|
auto res = ZeroFillNewImage(name, 0);
|
||
|
if (!res.is_ok()) {
|
||
|
DeleteBackingImage(name);
|
||
|
return res;
|
||
|
}
|
||
|
}
|
||
|
return FiemapStatus::Ok();
|
||
|
}
|
||
|
|
||
|
FiemapStatus ImageManager::ZeroFillNewImage(const std::string& name, uint64_t bytes) {
|
||
|
auto data_path = GetImageHeaderPath(name);
|
||
|
|
||
|
// See the comment in MapImageDevice() about how this works.
|
||
|
std::string block_device;
|
||
|
bool can_use_devicemapper;
|
||
|
if (!FiemapWriter::GetBlockDeviceForFile(data_path, &block_device, &can_use_devicemapper)) {
|
||
|
LOG(ERROR) << "Could not determine block device for " << data_path;
|
||
|
return FiemapStatus::Error();
|
||
|
}
|
||
|
|
||
|
if (!can_use_devicemapper) {
|
||
|
// We've backed with loop devices, and since we store files in an
|
||
|
// unencrypted folder, the initial zeroes we wrote will suffice.
|
||
|
return FiemapStatus::Ok();
|
||
|
}
|
||
|
|
||
|
// data is dm-crypt, or FBE + dm-default-key. This means the zeroes written
|
||
|
// by libfiemap were encrypted, so we need to map the image in and correct
|
||
|
// this.
|
||
|
auto device = MappedDevice::Open(this, 10s, name);
|
||
|
if (!device) {
|
||
|
return FiemapStatus::Error();
|
||
|
}
|
||
|
|
||
|
static constexpr size_t kChunkSize = 4096;
|
||
|
std::string zeroes(kChunkSize, '\0');
|
||
|
|
||
|
uint64_t remaining;
|
||
|
if (bytes) {
|
||
|
remaining = bytes;
|
||
|
} else {
|
||
|
remaining = get_block_device_size(device->fd());
|
||
|
if (!remaining) {
|
||
|
PLOG(ERROR) << "Could not get block device size for " << device->path();
|
||
|
return FiemapStatus::FromErrno(errno);
|
||
|
}
|
||
|
}
|
||
|
while (remaining) {
|
||
|
uint64_t to_write = std::min(static_cast<uint64_t>(zeroes.size()), remaining);
|
||
|
if (!android::base::WriteFully(device->fd(), zeroes.data(),
|
||
|
static_cast<size_t>(to_write))) {
|
||
|
PLOG(ERROR) << "write failed: " << device->path();
|
||
|
return FiemapStatus::FromErrno(errno);
|
||
|
}
|
||
|
remaining -= to_write;
|
||
|
}
|
||
|
return FiemapStatus::Ok();
|
||
|
}
|
||
|
|
||
|
bool ImageManager::DeleteBackingImage(const std::string& name) {
|
||
|
// For dm-linear devices sitting on top of /data, we cannot risk deleting
|
||
|
// the file. The underlying blocks could be reallocated by the filesystem.
|
||
|
if (IsImageMapped(name)) {
|
||
|
LOG(ERROR) << "Cannot delete backing image " << name << " because mapped to a block device";
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (device_info_.is_recovery.value()) {
|
||
|
LOG(ERROR) << "Cannot remove images backed by /data in recovery";
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
std::string message;
|
||
|
auto header_file = GetImageHeaderPath(name);
|
||
|
if (!SplitFiemap::RemoveSplitFiles(header_file, &message)) {
|
||
|
// This is fatal, because we don't want to leave these files dangling.
|
||
|
LOG(ERROR) << "Error removing image " << name << ": " << message;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
auto status_file = GetStatusFilePath(name);
|
||
|
if (!android::base::RemoveFileIfExists(status_file)) {
|
||
|
LOG(ERROR) << "Error removing " << status_file << ": " << message;
|
||
|
}
|
||
|
return RemoveImageMetadata(metadata_dir_, name);
|
||
|
}
|
||
|
|
||
|
// Create a block device for an image file, using its extents in its
|
||
|
// lp_metadata.
|
||
|
bool ImageManager::MapWithDmLinear(const IPartitionOpener& opener, const std::string& name,
|
||
|
const std::chrono::milliseconds& timeout_ms, std::string* path) {
|
||
|
// :TODO: refresh extents in metadata file until f2fs is fixed.
|
||
|
auto metadata = OpenMetadata(metadata_dir_);
|
||
|
if (!metadata) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
auto super = android::fs_mgr::GetMetadataSuperBlockDevice(*metadata.get());
|
||
|
auto block_device = android::fs_mgr::GetBlockDevicePartitionName(*super);
|
||
|
|
||
|
CreateLogicalPartitionParams params = {
|
||
|
.block_device = block_device,
|
||
|
.metadata = metadata.get(),
|
||
|
.partition_name = name,
|
||
|
.force_writable = true,
|
||
|
.timeout_ms = timeout_ms,
|
||
|
.partition_opener = &opener,
|
||
|
};
|
||
|
if (!CreateLogicalPartition(params, path)) {
|
||
|
LOG(ERROR) << "Error creating device-mapper node for image " << name;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
auto status_string = "dm:" + name;
|
||
|
auto status_file = GetStatusFilePath(name);
|
||
|
if (!android::base::WriteStringToFile(status_string, status_file)) {
|
||
|
PLOG(ERROR) << "Could not write status file: " << status_file;
|
||
|
DestroyLogicalPartition(name);
|
||
|
return false;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// Helper to create a loop device for a file.
|
||
|
static bool CreateLoopDevice(LoopControl& control, const std::string& file,
|
||
|
const std::chrono::milliseconds& timeout_ms, std::string* path) {
|
||
|
static constexpr int kOpenFlags = O_RDWR | O_NOFOLLOW | O_CLOEXEC;
|
||
|
android::base::unique_fd file_fd(open(file.c_str(), kOpenFlags));
|
||
|
if (file_fd < 0) {
|
||
|
PLOG(ERROR) << "Could not open file: " << file;
|
||
|
return false;
|
||
|
}
|
||
|
if (!control.Attach(file_fd, timeout_ms, path)) {
|
||
|
LOG(ERROR) << "Could not create loop device for: " << file;
|
||
|
return false;
|
||
|
}
|
||
|
LOG(INFO) << "Created loop device " << *path << " for file " << file;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
class AutoDetachLoopDevices final {
|
||
|
public:
|
||
|
AutoDetachLoopDevices(LoopControl& control, const std::vector<std::string>& devices)
|
||
|
: control_(control), devices_(devices), commit_(false) {}
|
||
|
|
||
|
~AutoDetachLoopDevices() {
|
||
|
if (commit_) return;
|
||
|
for (const auto& device : devices_) {
|
||
|
control_.Detach(device);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void Commit() { commit_ = true; }
|
||
|
|
||
|
private:
|
||
|
LoopControl& control_;
|
||
|
const std::vector<std::string>& devices_;
|
||
|
bool commit_;
|
||
|
};
|
||
|
|
||
|
// If an image is stored across multiple files, this takes a list of loop
|
||
|
// devices and joins them together using device-mapper.
|
||
|
bool ImageManager::MapWithLoopDeviceList(const std::vector<std::string>& device_list,
|
||
|
const std::string& name,
|
||
|
const std::chrono::milliseconds& timeout_ms,
|
||
|
std::string* path) {
|
||
|
auto metadata = OpenMetadata(metadata_dir_);
|
||
|
if (!metadata) {
|
||
|
return false;
|
||
|
}
|
||
|
auto partition = FindPartition(*metadata.get(), name);
|
||
|
if (!partition) {
|
||
|
LOG(ERROR) << "Could not find image in metadata: " << name;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Since extent lengths are in sector units, the size should be a multiple
|
||
|
// of the sector size.
|
||
|
uint64_t partition_size = GetPartitionSize(*metadata.get(), *partition);
|
||
|
if (partition_size % LP_SECTOR_SIZE != 0) {
|
||
|
LOG(ERROR) << "Partition size not sector aligned: " << name << ", " << partition_size
|
||
|
<< " bytes";
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
DmTable table;
|
||
|
|
||
|
uint64_t start_sector = 0;
|
||
|
uint64_t sectors_needed = partition_size / LP_SECTOR_SIZE;
|
||
|
for (const auto& block_device : device_list) {
|
||
|
// The final block device must be == partition_size, otherwise we
|
||
|
// can't find the AVB footer on verified partitions.
|
||
|
static constexpr int kOpenFlags = O_RDWR | O_NOFOLLOW | O_CLOEXEC;
|
||
|
unique_fd fd(open(block_device.c_str(), kOpenFlags));
|
||
|
if (fd < 0) {
|
||
|
PLOG(ERROR) << "Open failed: " << block_device;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
uint64_t file_size = get_block_device_size(fd);
|
||
|
uint64_t file_sectors = file_size / LP_SECTOR_SIZE;
|
||
|
uint64_t segment_size = std::min(file_sectors, sectors_needed);
|
||
|
|
||
|
table.Emplace<DmTargetLinear>(start_sector, segment_size, block_device, 0);
|
||
|
|
||
|
start_sector += segment_size;
|
||
|
sectors_needed -= segment_size;
|
||
|
if (sectors_needed == 0) {
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
auto& dm = DeviceMapper::Instance();
|
||
|
if (!dm.CreateDevice(name, table, path, timeout_ms)) {
|
||
|
LOG(ERROR) << "Could not create device-mapper device over loop set";
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Build the status file.
|
||
|
std::vector<std::string> lines;
|
||
|
lines.emplace_back("dm:" + name);
|
||
|
for (const auto& block_device : device_list) {
|
||
|
lines.emplace_back("loop:" + block_device);
|
||
|
}
|
||
|
auto status_message = android::base::Join(lines, "\n");
|
||
|
auto status_file = GetStatusFilePath(name);
|
||
|
if (!android::base::WriteStringToFile(status_message, status_file)) {
|
||
|
PLOG(ERROR) << "Write failed: " << status_file;
|
||
|
dm.DeleteDevice(name);
|
||
|
return false;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
static bool OptimizeLoopDevices(const std::vector<std::string>& device_list) {
|
||
|
for (const auto& device : device_list) {
|
||
|
unique_fd fd(open(device.c_str(), O_RDWR | O_CLOEXEC | O_NOFOLLOW));
|
||
|
if (fd < 0) {
|
||
|
PLOG(ERROR) << "Open failed: " << device;
|
||
|
return false;
|
||
|
}
|
||
|
if (!LoopControl::EnableDirectIo(fd)) {
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
// Helper to use one or more loop devices around image files.
|
||
|
bool ImageManager::MapWithLoopDevice(const std::string& name,
|
||
|
const std::chrono::milliseconds& timeout_ms,
|
||
|
std::string* path) {
|
||
|
auto image_header = GetImageHeaderPath(name);
|
||
|
|
||
|
std::vector<std::string> file_list;
|
||
|
if (!SplitFiemap::GetSplitFileList(image_header, &file_list)) {
|
||
|
LOG(ERROR) << "Could not get image file list";
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Map each image file as a loopback device.
|
||
|
LoopControl control;
|
||
|
std::vector<std::string> loop_devices;
|
||
|
AutoDetachLoopDevices auto_detach(control, loop_devices);
|
||
|
|
||
|
auto start_time = std::chrono::steady_clock::now();
|
||
|
for (const auto& file : file_list) {
|
||
|
auto now = std::chrono::steady_clock::now();
|
||
|
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - start_time);
|
||
|
|
||
|
std::string loop_device;
|
||
|
if (!CreateLoopDevice(control, file, timeout_ms - elapsed, &loop_device)) {
|
||
|
break;
|
||
|
}
|
||
|
loop_devices.emplace_back(loop_device);
|
||
|
}
|
||
|
if (loop_devices.size() != file_list.size()) {
|
||
|
// The number of devices will mismatch if CreateLoopDevice() failed.
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// If OptimizeLoopDevices fails, we'd use double the memory.
|
||
|
if (!OptimizeLoopDevices(loop_devices)) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// If there's only one loop device (by far the most common case, splits
|
||
|
// will normally only happen on sdcards with FAT32), then just return that
|
||
|
// as the block device. Otherwise, we need to use dm-linear to stitch
|
||
|
// together all the loop devices we just created.
|
||
|
if (loop_devices.size() > 1) {
|
||
|
if (!MapWithLoopDeviceList(loop_devices, name, timeout_ms, path)) {
|
||
|
return false;
|
||
|
}
|
||
|
} else {
|
||
|
auto status_message = "loop:" + loop_devices.back();
|
||
|
auto status_file = GetStatusFilePath(name);
|
||
|
if (!android::base::WriteStringToFile(status_message, status_file)) {
|
||
|
PLOG(ERROR) << "Write failed: " << status_file;
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
auto_detach.Commit();
|
||
|
|
||
|
*path = loop_devices.back();
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
bool ImageManager::MapImageDevice(const std::string& name,
|
||
|
const std::chrono::milliseconds& timeout_ms, std::string* path) {
|
||
|
if (IsImageMapped(name)) {
|
||
|
LOG(ERROR) << "Backing image " << name << " is already mapped";
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
auto image_header = GetImageHeaderPath(name);
|
||
|
|
||
|
#ifndef __ANDROID_RAMDISK__
|
||
|
// If there is a device-mapper node wrapping the block device, then we're
|
||
|
// able to create another node around it; the dm layer does not carry the
|
||
|
// exclusion lock down the stack when a mount occurs.
|
||
|
//
|
||
|
// If there is no intermediate device-mapper node, then partitions cannot be
|
||
|
// opened writable due to sepolicy and exclusivity of having a mounted
|
||
|
// filesystem. This should only happen on devices with no encryption, or
|
||
|
// devices with FBE and no metadata encryption. For these cases it suffices
|
||
|
// to perform normal file writes to /data/gsi (which is unencrypted).
|
||
|
//
|
||
|
// Note: this is not gated on DeviceInfo, because the recovery-specific path
|
||
|
// must only be used in actual recovery.
|
||
|
std::string block_device;
|
||
|
bool can_use_devicemapper;
|
||
|
if (!FiemapWriter::GetBlockDeviceForFile(image_header, &block_device, &can_use_devicemapper)) {
|
||
|
LOG(ERROR) << "Could not determine block device for " << image_header;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (can_use_devicemapper) {
|
||
|
if (!MapWithDmLinear(*partition_opener_.get(), name, timeout_ms, path)) {
|
||
|
return false;
|
||
|
}
|
||
|
} else if (!MapWithLoopDevice(name, timeout_ms, path)) {
|
||
|
return false;
|
||
|
}
|
||
|
#else
|
||
|
// In recovery, we can *only* use device-mapper, since partitions aren't
|
||
|
// mounted. That also means we cannot call GetBlockDeviceForFile.
|
||
|
if (!MapWithDmLinear(*partition_opener_.get(), name, timeout_ms, path)) {
|
||
|
return false;
|
||
|
}
|
||
|
#endif
|
||
|
|
||
|
// Set a property so we remember this is mapped.
|
||
|
auto prop_name = GetStatusPropertyName(name);
|
||
|
if (!android::base::SetProperty(prop_name, *path)) {
|
||
|
UnmapImageDevice(name, true);
|
||
|
return false;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
bool ImageManager::MapImageWithDeviceMapper(const IPartitionOpener& opener, const std::string& name,
|
||
|
std::string* dev) {
|
||
|
std::string ignore_path;
|
||
|
if (!MapWithDmLinear(opener, name, {}, &ignore_path)) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
auto& dm = DeviceMapper::Instance();
|
||
|
if (!dm.GetDeviceString(name, dev)) {
|
||
|
return false;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
bool ImageManager::UnmapImageDevice(const std::string& name) {
|
||
|
return UnmapImageDevice(name, false);
|
||
|
}
|
||
|
|
||
|
bool ImageManager::UnmapImageDevice(const std::string& name, bool force) {
|
||
|
if (!force && !IsImageMapped(name)) {
|
||
|
LOG(ERROR) << "Backing image " << name << " is not mapped";
|
||
|
return false;
|
||
|
}
|
||
|
auto& dm = DeviceMapper::Instance();
|
||
|
std::optional<LoopControl> loop;
|
||
|
|
||
|
std::string status;
|
||
|
auto status_file = GetStatusFilePath(name);
|
||
|
if (!android::base::ReadFileToString(status_file, &status)) {
|
||
|
PLOG(ERROR) << "Read failed: " << status_file;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
auto lines = android::base::Split(status, "\n");
|
||
|
for (const auto& line : lines) {
|
||
|
auto pieces = android::base::Split(line, ":");
|
||
|
if (pieces.size() != 2) {
|
||
|
LOG(ERROR) << "Unknown status line";
|
||
|
continue;
|
||
|
}
|
||
|
if (pieces[0] == "dm") {
|
||
|
// Failure to remove a dm node is fatal, since we can't safely
|
||
|
// remove the file or loop devices.
|
||
|
const auto& name = pieces[1];
|
||
|
if (!dm.DeleteDeviceIfExists(name)) {
|
||
|
return false;
|
||
|
}
|
||
|
} else if (pieces[0] == "loop") {
|
||
|
// Lazily connect to loop-control to avoid spurious errors in recovery.
|
||
|
if (!loop.has_value()) {
|
||
|
loop.emplace();
|
||
|
}
|
||
|
|
||
|
// Failure to remove a loop device is not fatal, since we can still
|
||
|
// remove the backing file if we want.
|
||
|
loop->Detach(pieces[1]);
|
||
|
} else {
|
||
|
LOG(ERROR) << "Unknown status: " << pieces[0];
|
||
|
}
|
||
|
}
|
||
|
|
||
|
std::string message;
|
||
|
if (!android::base::RemoveFileIfExists(status_file, &message)) {
|
||
|
LOG(ERROR) << "Could not remove " << status_file << ": " << message;
|
||
|
}
|
||
|
|
||
|
auto status_prop = GetStatusPropertyName(name);
|
||
|
android::base::SetProperty(status_prop, "");
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
bool ImageManager::RemoveAllImages() {
|
||
|
if (!MetadataExists(metadata_dir_)) {
|
||
|
return true;
|
||
|
}
|
||
|
auto metadata = OpenMetadata(metadata_dir_);
|
||
|
if (!metadata) {
|
||
|
return RemoveAllMetadata(metadata_dir_);
|
||
|
}
|
||
|
|
||
|
bool ok = true;
|
||
|
for (const auto& partition : metadata->partitions) {
|
||
|
auto partition_name = GetPartitionName(partition);
|
||
|
ok &= DeleteBackingImage(partition_name);
|
||
|
}
|
||
|
return ok && RemoveAllMetadata(metadata_dir_);
|
||
|
}
|
||
|
|
||
|
bool ImageManager::Validate() {
|
||
|
auto metadata = OpenMetadata(metadata_dir_);
|
||
|
if (!metadata) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
bool ok = true;
|
||
|
for (const auto& partition : metadata->partitions) {
|
||
|
auto name = GetPartitionName(partition);
|
||
|
auto image_path = GetImageHeaderPath(name);
|
||
|
auto fiemap = SplitFiemap::Open(image_path);
|
||
|
if (fiemap == nullptr) {
|
||
|
LOG(ERROR) << "SplitFiemap::Open(\"" << image_path << "\") failed";
|
||
|
ok = false;
|
||
|
continue;
|
||
|
}
|
||
|
if (!fiemap->HasPinnedExtents()) {
|
||
|
LOG(ERROR) << "Image doesn't have pinned extents: " << image_path;
|
||
|
ok = false;
|
||
|
}
|
||
|
}
|
||
|
return ok;
|
||
|
}
|
||
|
|
||
|
bool ImageManager::DisableImage(const std::string& name) {
|
||
|
return AddAttributes(metadata_dir_, name, LP_PARTITION_ATTR_DISABLED);
|
||
|
}
|
||
|
|
||
|
bool ImageManager::RemoveDisabledImages() {
|
||
|
if (!MetadataExists(metadata_dir_)) {
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
auto metadata = OpenMetadata(metadata_dir_);
|
||
|
if (!metadata) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
bool ok = true;
|
||
|
for (const auto& partition : metadata->partitions) {
|
||
|
if (partition.attributes & LP_PARTITION_ATTR_DISABLED) {
|
||
|
const auto name = GetPartitionName(partition);
|
||
|
if (!DeleteBackingImage(name)) {
|
||
|
ok = false;
|
||
|
} else {
|
||
|
LOG(INFO) << "Removed disabled partition image: " << name;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return ok;
|
||
|
}
|
||
|
|
||
|
bool ImageManager::GetMappedImageDevice(const std::string& name, std::string* device) {
|
||
|
auto prop_name = GetStatusPropertyName(name);
|
||
|
*device = android::base::GetProperty(prop_name, "");
|
||
|
if (!device->empty()) {
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
auto& dm = DeviceMapper::Instance();
|
||
|
if (dm.GetState(name) == DmDeviceState::INVALID) {
|
||
|
return false;
|
||
|
}
|
||
|
return dm.GetDmDevicePathByName(name, device);
|
||
|
}
|
||
|
|
||
|
bool ImageManager::MapAllImages(const std::function<bool(std::set<std::string>)>& init) {
|
||
|
if (!MetadataExists(metadata_dir_)) {
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
auto metadata = OpenMetadata(metadata_dir_);
|
||
|
if (!metadata) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
std::set<std::string> devices;
|
||
|
for (const auto& name : GetBlockDevicePartitionNames(*metadata.get())) {
|
||
|
devices.emplace(name);
|
||
|
}
|
||
|
if (!init(std::move(devices))) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
auto data_device = GetMetadataSuperBlockDevice(*metadata.get());
|
||
|
auto data_partition_name = GetBlockDevicePartitionName(*data_device);
|
||
|
return CreateLogicalPartitions(*metadata.get(), data_partition_name);
|
||
|
}
|
||
|
|
||
|
std::ostream& operator<<(std::ostream& os, android::fs_mgr::Extent* extent) {
|
||
|
if (auto e = extent->AsLinearExtent()) {
|
||
|
return os << "<begin:" << e->physical_sector() << ", end:" << e->end_sector()
|
||
|
<< ", device:" << e->device_index() << ">";
|
||
|
}
|
||
|
return os << "<unknown>";
|
||
|
}
|
||
|
|
||
|
static bool CompareExtent(android::fs_mgr::Extent* a, android::fs_mgr::Extent* b) {
|
||
|
if (auto linear_a = a->AsLinearExtent()) {
|
||
|
auto linear_b = b->AsLinearExtent();
|
||
|
if (!linear_b) {
|
||
|
return false;
|
||
|
}
|
||
|
return linear_a->physical_sector() == linear_b->physical_sector() &&
|
||
|
linear_a->num_sectors() == linear_b->num_sectors() &&
|
||
|
linear_a->device_index() == linear_b->device_index();
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
static bool CompareExtents(android::fs_mgr::Partition* oldp, android::fs_mgr::Partition* newp) {
|
||
|
const auto& old_extents = oldp->extents();
|
||
|
const auto& new_extents = newp->extents();
|
||
|
|
||
|
auto old_iter = old_extents.begin();
|
||
|
auto new_iter = new_extents.begin();
|
||
|
while (true) {
|
||
|
if (old_iter == old_extents.end()) {
|
||
|
if (new_iter == new_extents.end()) {
|
||
|
break;
|
||
|
}
|
||
|
LOG(ERROR) << "Unexpected extent added: " << (*new_iter);
|
||
|
return false;
|
||
|
}
|
||
|
if (new_iter == new_extents.end()) {
|
||
|
LOG(ERROR) << "Unexpected extent removed: " << (*old_iter);
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (!CompareExtent(old_iter->get(), new_iter->get())) {
|
||
|
LOG(ERROR) << "Extents do not match: " << old_iter->get() << ", " << new_iter->get();
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
old_iter++;
|
||
|
new_iter++;
|
||
|
}
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
bool ImageManager::ValidateImageMaps() {
|
||
|
if (!MetadataExists(metadata_dir_)) {
|
||
|
LOG(INFO) << "ImageManager skipping verification; no images for " << metadata_dir_;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
auto metadata = OpenMetadata(metadata_dir_);
|
||
|
if (!metadata) {
|
||
|
LOG(ERROR) << "ImageManager skipping verification; failed to open " << metadata_dir_;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
for (const auto& partition : metadata->partitions) {
|
||
|
auto name = GetPartitionName(partition);
|
||
|
auto image_path = GetImageHeaderPath(name);
|
||
|
auto fiemap = SplitFiemap::Open(image_path);
|
||
|
if (fiemap == nullptr) {
|
||
|
LOG(ERROR) << "SplitFiemap::Open(\"" << image_path << "\") failed";
|
||
|
return false;
|
||
|
}
|
||
|
if (!fiemap->HasPinnedExtents()) {
|
||
|
LOG(ERROR) << "Image doesn't have pinned extents: " << image_path;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
android::fs_mgr::PartitionOpener opener;
|
||
|
auto builder = android::fs_mgr::MetadataBuilder::New(*metadata.get(), &opener);
|
||
|
if (!builder) {
|
||
|
LOG(ERROR) << "Could not create metadata builder: " << image_path;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
auto new_p = builder->AddPartition("_temp_for_verify", 0);
|
||
|
if (!new_p) {
|
||
|
LOG(ERROR) << "Could not add temporary partition: " << image_path;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
auto partition_size = android::fs_mgr::GetPartitionSize(*metadata.get(), partition);
|
||
|
if (!FillPartitionExtents(builder.get(), new_p, fiemap.get(), partition_size)) {
|
||
|
LOG(ERROR) << "Could not fill partition extents: " << image_path;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
auto old_p = builder->FindPartition(name);
|
||
|
if (!old_p) {
|
||
|
LOG(ERROR) << "Could not find metadata for " << image_path;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (!CompareExtents(old_p, new_p)) {
|
||
|
LOG(ERROR) << "Metadata for " << image_path << " does not match fiemap";
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
bool ImageManager::IsImageDisabled(const std::string& name) {
|
||
|
if (!MetadataExists(metadata_dir_)) {
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
auto metadata = OpenMetadata(metadata_dir_);
|
||
|
if (!metadata) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
auto partition = FindPartition(*metadata.get(), name);
|
||
|
if (!partition) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return !!(partition->attributes & LP_PARTITION_ATTR_DISABLED);
|
||
|
}
|
||
|
|
||
|
std::unique_ptr<MappedDevice> MappedDevice::Open(IImageManager* manager,
|
||
|
const std::chrono::milliseconds& timeout_ms,
|
||
|
const std::string& name) {
|
||
|
std::string path;
|
||
|
if (!manager->MapImageDevice(name, timeout_ms, &path)) {
|
||
|
return nullptr;
|
||
|
}
|
||
|
|
||
|
auto device = std::unique_ptr<MappedDevice>(new MappedDevice(manager, name, path));
|
||
|
if (device->fd() < 0) {
|
||
|
return nullptr;
|
||
|
}
|
||
|
return device;
|
||
|
}
|
||
|
|
||
|
MappedDevice::MappedDevice(IImageManager* manager, const std::string& name, const std::string& path)
|
||
|
: manager_(manager), name_(name), path_(path) {
|
||
|
// The device is already mapped; try and open it.
|
||
|
fd_.reset(open(path.c_str(), O_RDWR | O_CLOEXEC));
|
||
|
}
|
||
|
|
||
|
MappedDevice::~MappedDevice() {
|
||
|
fd_ = {};
|
||
|
manager_->UnmapImageDevice(name_);
|
||
|
}
|
||
|
|
||
|
bool IImageManager::UnmapImageIfExists(const std::string& name) {
|
||
|
// No lock is needed even though this seems to be vulnerable to TOCTOU. If process A
|
||
|
// calls MapImageDevice() while process B calls UnmapImageIfExists(), and MapImageDevice()
|
||
|
// happens after process B checks IsImageMapped(), it would be as if MapImageDevice() is called
|
||
|
// after process B finishes calling UnmapImageIfExists(), resulting the image to be mapped,
|
||
|
// which is a reasonable sequence.
|
||
|
if (!IsImageMapped(name)) {
|
||
|
return true;
|
||
|
}
|
||
|
return UnmapImageDevice(name);
|
||
|
}
|
||
|
|
||
|
} // namespace fiemap
|
||
|
} // namespace android
|