diff options
-rw-r--r-- | Android.bp | 1 | ||||
-rw-r--r-- | file_paths.h | 3 | ||||
-rw-r--r-- | gsi_installer.cpp | 665 | ||||
-rw-r--r-- | gsi_installer.h | 124 | ||||
-rw-r--r-- | gsi_service.cpp | 681 | ||||
-rw-r--r-- | gsi_service.h | 79 |
6 files changed, 836 insertions, 717 deletions
@@ -57,6 +57,7 @@ cc_binary { srcs: [ "daemon.cpp", "gsi_service.cpp", + "gsi_installer.cpp", ], required: [ "mke2fs", diff --git a/file_paths.h b/file_paths.h index 64814b5..5a16491 100644 --- a/file_paths.h +++ b/file_paths.h @@ -19,6 +19,9 @@ namespace android { namespace gsi { +static constexpr char kGsiDataFolder[] = "/data/gsi/"; +static constexpr char kUserdataDevice[] = "/dev/block/by-name/userdata"; + static constexpr char kGsiMetadataFolder[] = "/metadata/gsi"; static constexpr char kGsiLpMetadataFile[] = "/metadata/gsi/lp_metadata"; static constexpr char kGsiOneShotBootFile[] = "/metadata/gsi/one_shot_boot"; diff --git a/gsi_installer.cpp b/gsi_installer.cpp new file mode 100644 index 0000000..0268746 --- /dev/null +++ b/gsi_installer.cpp @@ -0,0 +1,665 @@ +/* + * 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 "gsi_installer.h" + +#include <sys/statvfs.h> + +#include <android-base/file.h> +#include <android-base/logging.h> +#include <android-base/unique_fd.h> +#include <fs_mgr_dm_linear.h> +#include <libdm/dm.h> +#include <libgsi/libgsi.h> + +#include "file_paths.h" +#include "gsi_service.h" +#include "libgsi_private.h" + +namespace android { +namespace gsi { + +using namespace std::literals; +using namespace android::dm; +using namespace android::fiemap_writer; +using namespace android::fs_mgr; +using android::base::unique_fd; + +// The default size of userdata.img for GSI. +// We are looking for /data to have atleast 40% free space +static constexpr uint32_t kMinimumFreeSpaceThreshold = 40; +// We determine the fragmentation by making sure the files +// we create don't have more than 16 extents. +static constexpr uint32_t kMaximumExtents = 512; +// Default userdata image size. +static constexpr int64_t kDefaultUserdataSize = int64_t(8) * 1024 * 1024 * 1024; +static constexpr std::chrono::milliseconds kDmTimeout = 5000ms; + +GsiInstaller::GsiInstaller(GsiService* service, const GsiInstallParams& params) + : service_(service), + install_dir_(params.installDir), + gsi_size_(params.gsiSize), + wipe_userdata_(params.wipeUserdata) { + userdata_size_ = (params.userdataSize) ? params.userdataSize : kDefaultUserdataSize; + userdata_gsi_path_ = GetImagePath("userdata_gsi"); + system_gsi_path_ = GetImagePath("system_gsi"); + + // Only rm userdata_gsi if one didn't already exist. + wipe_userdata_on_failure_ = wipe_userdata_ || access(userdata_gsi_path_.c_str(), F_OK); +} + +GsiInstaller::GsiInstaller(GsiService* service, const std::string& install_dir) + : service_(service), install_dir_(install_dir) { + system_gsi_path_ = GetImagePath("system_gsi"); +} + +GsiInstaller::~GsiInstaller() { + if (!succeeded_) { + // Close open handles before we remove files. + system_writer_ = nullptr; + partitions_.clear(); + PostInstallCleanup(); + + GsiService::RemoveGsiFiles(install_dir_, wipe_userdata_on_failure_); + } +} + +void GsiInstaller::PostInstallCleanup() { + const auto& dm = DeviceMapper::Instance(); + if (dm.GetState("userdata_gsi") != DmDeviceState::INVALID) { + DestroyLogicalPartition("userdata_gsi", kDmTimeout); + } + if (dm.GetState("system_gsi") != DmDeviceState::INVALID) { + DestroyLogicalPartition("system_gsi", kDmTimeout); + } +} + +int GsiInstaller::StartInstall() { + if (int status = PerformSanityChecks()) { + return status; + } + if (int status = PreallocateFiles()) { + return status; + } + if (int status = DetermineReadWriteMethod()) { + return status; + } + if (!FormatUserdata()) { + return IGsiService::INSTALL_ERROR_GENERIC; + } + + // Map system_gsi so we can write to it. + system_writer_ = OpenPartition("system_gsi"); + if (!system_writer_) { + return IGsiService::INSTALL_ERROR_GENERIC; + } + + // Clear the progress indicator. + service_->UpdateProgress(IGsiService::STATUS_NO_OPERATION, 0); + return IGsiService::INSTALL_OK; +} + +int GsiInstaller::DetermineReadWriteMethod() { + // 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). + std::string block_device; + if (!FiemapWriter::GetBlockDeviceForFile(system_gsi_path_.c_str(), &block_device, + &can_use_devicemapper_)) { + return IGsiService::INSTALL_ERROR_GENERIC; + } + if (install_dir_ != kGsiDataFolder && can_use_devicemapper_) { + // Never use device-mapper on external media. We don't support adopted + // storage yet, and accidentally using device-mapper could be dangerous + // as we hardcode the userdata device as backing storage. + LOG(ERROR) << "unexpected device-mapper node used to mount external media"; + return IGsiService::INSTALL_ERROR_GENERIC; + } + return IGsiService::INSTALL_OK; +} + +int GsiInstaller::PerformSanityChecks() { + if (gsi_size_ < 0) { + LOG(ERROR) << "image size " << gsi_size_ << " is negative"; + return IGsiService::INSTALL_ERROR_GENERIC; + } + if (android::gsi::IsGsiRunning()) { + LOG(ERROR) << "cannot install gsi inside a live gsi"; + return IGsiService::INSTALL_ERROR_GENERIC; + } + + struct statvfs sb; + if (statvfs(install_dir_.c_str(), &sb)) { + PLOG(ERROR) << "failed to read file system stats"; + return IGsiService::INSTALL_ERROR_GENERIC; + } + + // This is the same as android::vold::GetFreebytes() but we also + // need the total file system size so we open code it here. + uint64_t free_space = sb.f_bavail * sb.f_frsize; + uint64_t fs_size = sb.f_blocks * sb.f_frsize; + if (free_space <= (gsi_size_ + userdata_size_)) { + LOG(ERROR) << "not enough free space (only " << free_space << " bytes available)"; + return IGsiService::INSTALL_ERROR_NO_SPACE; + } + // We are asking for 40% of the /data to be empty. + // TODO: may be not hard code it like this + double free_space_percent = ((1.0 * free_space) / fs_size) * 100; + if (free_space_percent < kMinimumFreeSpaceThreshold) { + LOG(ERROR) << "free space " << static_cast<uint64_t>(free_space_percent) + << "% is below the minimum threshold of " << kMinimumFreeSpaceThreshold << "%"; + return IGsiService::INSTALL_ERROR_FILE_SYSTEM_CLUTTERED; + } + return IGsiService::INSTALL_OK; +} + +int GsiInstaller::PreallocateFiles() { + if (wipe_userdata_) { + SplitFiemap::RemoveSplitFiles(userdata_gsi_path_); + } + SplitFiemap::RemoveSplitFiles(system_gsi_path_); + + // TODO: trigger GC from fiemap writer. + + // Create fallocated files. + if (int status = PreallocateUserdata()) { + return status; + } + if (int status = PreallocateSystem()) { + return status; + } + + // Save the extent information in liblp. + metadata_ = CreateMetadata(); + if (!metadata_) { + return IGsiService::INSTALL_ERROR_GENERIC; + } + + service_->UpdateProgress(IGsiService::STATUS_COMPLETE, 0); + return IGsiService::INSTALL_OK; +} + +int GsiInstaller::PreallocateUserdata() { + int error; + std::unique_ptr<SplitFiemap> userdata_image; + if (wipe_userdata_ || access(userdata_gsi_path_.c_str(), F_OK)) { + service_->StartAsyncOperation("create userdata", userdata_size_); + userdata_image = CreateFiemapWriter(userdata_gsi_path_, userdata_size_, &error); + if (!userdata_image) { + LOG(ERROR) << "Could not create userdata image: " << userdata_gsi_path_; + return error; + } + // Signal that we need to reformat userdata. + wipe_userdata_ = true; + } else { + userdata_image = CreateFiemapWriter(userdata_gsi_path_, 0, &error); + if (!userdata_image) { + LOG(ERROR) << "Could not open userdata image: " << userdata_gsi_path_; + return error; + } + if (userdata_size_ && userdata_image->size() < userdata_size_) { + // :TODO: need to fallocate more blocks and resizefs. + } + userdata_size_ = userdata_image->size(); + } + + userdata_block_size_ = userdata_image->block_size(); + + Image image = { + .writer = std::move(userdata_image), + .actual_size = userdata_size_, + }; + partitions_.emplace(std::make_pair("userdata_gsi", std::move(image))); + return IGsiService::INSTALL_OK; +} + +int GsiInstaller::PreallocateSystem() { + service_->StartAsyncOperation("create system", gsi_size_); + + int error; + auto system_image = CreateFiemapWriter(system_gsi_path_, gsi_size_, &error); + if (!system_image) { + return error; + } + + system_block_size_ = system_image->block_size(); + + Image image = { + .writer = std::move(system_image), + .actual_size = gsi_size_, + }; + partitions_.emplace(std::make_pair("system_gsi", std::move(image))); + return IGsiService::INSTALL_OK; +} + +std::unique_ptr<SplitFiemap> GsiInstaller::CreateFiemapWriter(const std::string& path, + uint64_t size, int* error) { + bool create = (size != 0); + + std::function<bool(uint64_t, uint64_t)> progress; + if (create) { + progress = [this](uint64_t bytes, uint64_t /* total */) -> bool { + service_->UpdateProgress(IGsiService::STATUS_WORKING, bytes); + if (service_->should_abort()) return false; + return true; + }; + } + + std::unique_ptr<SplitFiemap> file; + if (!size) { + file = SplitFiemap::Open(path); + } else { + file = SplitFiemap::Create(path, size, 0, std::move(progress)); + } + if (!file) { + LOG(ERROR) << "failed to create or open " << path; + *error = IGsiService::INSTALL_ERROR_GENERIC; + return nullptr; + } + + uint64_t extents = file->extents().size(); + if (extents > kMaximumExtents) { + LOG(ERROR) << "file " << path << " has too many extents: " << extents; + *error = IGsiService::INSTALL_ERROR_FILE_SYSTEM_CLUTTERED; + return nullptr; + } + return file; +} + +// Write data through an fd. +class FdWriter final : public GsiInstaller::WriteHelper { + public: + FdWriter(const std::string& path, unique_fd&& fd) : path_(path), fd_(std::move(fd)) {} + + bool Write(const void* data, uint64_t bytes) override { + return android::base::WriteFully(fd_, data, bytes); + } + bool Flush() override { + if (fsync(fd_)) { + PLOG(ERROR) << "fsync failed: " << path_; + return false; + } + return true; + } + + private: + std::string path_; + unique_fd fd_; +}; + +// Write data through a SplitFiemap. +class SplitFiemapWriter final : public GsiInstaller::WriteHelper { + public: + explicit SplitFiemapWriter(SplitFiemap* writer) : writer_(writer) {} + + bool Write(const void* data, uint64_t bytes) override { return writer_->Write(data, bytes); } + bool Flush() override { return writer_->Flush(); } + + private: + SplitFiemap* writer_; +}; + +std::unique_ptr<GsiInstaller::WriteHelper> GsiInstaller::OpenPartition(const std::string& name) { + if (can_use_devicemapper_) { + std::string path; + if (!CreateLogicalPartition(kUserdataDevice, *metadata_.get(), name, true, kDmTimeout, + &path)) { + LOG(ERROR) << "Error creating device-mapper node for " << name; + return {}; + } + + static const int kOpenFlags = O_RDWR | O_NOFOLLOW | O_CLOEXEC; + unique_fd fd(open(path.c_str(), kOpenFlags)); + if (fd < 0) { + PLOG(ERROR) << "could not open " << path; + } + return std::make_unique<FdWriter>(GetImagePath(name), std::move(fd)); + } + + auto iter = partitions_.find(name); + if (iter == partitions_.end()) { + LOG(ERROR) << "could not find partition " << name; + return {}; + } + return std::make_unique<SplitFiemapWriter>(iter->second.writer.get()); +} + +bool GsiInstaller::CommitGsiChunk(int stream_fd, int64_t bytes) { + service_->StartAsyncOperation("write gsi", gsi_size_); + + if (bytes < 0) { + LOG(ERROR) << "chunk size " << bytes << " is negative"; + return false; + } + + auto buffer = std::make_unique<char[]>(system_block_size_); + + int progress = -1; + uint64_t remaining = bytes; + while (remaining) { + // :TODO: check file pin status! + size_t max_to_read = std::min(system_block_size_, remaining); + ssize_t rv = TEMP_FAILURE_RETRY(read(stream_fd, buffer.get(), max_to_read)); + if (rv < 0) { + PLOG(ERROR) << "read gsi chunk"; + return false; + } + if (rv == 0) { + LOG(ERROR) << "no bytes left in stream"; + return false; + } + if (!CommitGsiChunk(buffer.get(), rv)) { + return false; + } + CHECK(static_cast<uint64_t>(rv) <= remaining); + remaining -= rv; + + // Only update the progress when the % (or permille, in this case) + // significantly changes. + int new_progress = ((gsi_size_ - remaining) * 1000) / gsi_size_; + if (new_progress != progress) { + service_->UpdateProgress(IGsiService::STATUS_WORKING, gsi_size_ - remaining); + } + } + + service_->UpdateProgress(IGsiService::STATUS_COMPLETE, gsi_size_); + return true; +} + +bool GsiInstaller::CommitGsiChunk(const void* data, size_t bytes) { + if (static_cast<uint64_t>(bytes) > gsi_size_ - gsi_bytes_written_) { + // We cannot write past the end of the image file. + LOG(ERROR) << "chunk size " << bytes << " exceeds remaining image size (" << gsi_size_ + << " expected, " << gsi_bytes_written_ << " written)"; + return false; + } + if (service_->should_abort()) { + return false; + } + if (!system_writer_->Write(data, bytes)) { + PLOG(ERROR) << "write failed"; + return false; + } + gsi_bytes_written_ += bytes; + return true; +} + +bool GsiInstaller::SetBootMode(bool one_shot) { + if (one_shot) { + if (!android::base::WriteStringToFile("1", kGsiOneShotBootFile)) { + PLOG(ERROR) << "write " << kGsiOneShotBootFile; + return false; + } + } else if (!access(kGsiOneShotBootFile, F_OK)) { + std::string error; + if (!android::base::RemoveFileIfExists(kGsiOneShotBootFile, &error)) { + LOG(ERROR) << error; + return false; + } + } + return true; +} + +std::string GsiInstaller::GetImagePath(const std::string& name) { + return GsiService::GetImagePath(install_dir_, name); +} + +bool GsiInstaller::CreateInstallStatusFile() { + if (!android::base::WriteStringToFile("0", kGsiInstallStatusFile)) { + PLOG(ERROR) << "write " << kGsiInstallStatusFile; + return false; + } + return true; +} + +std::unique_ptr<LpMetadata> GsiInstaller::CreateMetadata() { + std::string data_device_path; + if (install_dir_ == kGsiDataFolder && !access(kUserdataDevice, F_OK)) { + data_device_path = kUserdataDevice; + } else { + auto writer = partitions_["system_gsi"].writer.get(); + data_device_path = writer->bdev_path(); + } + auto data_device_name = android::base::Basename(data_device_path); + + PartitionOpener opener; + BlockDeviceInfo data_device_info; + if (!opener.GetInfo(data_device_path, &data_device_info)) { + LOG(ERROR) << "Error reading userdata partition"; + return nullptr; + } + + std::vector<BlockDeviceInfo> block_devices = {data_device_info}; + auto builder = MetadataBuilder::New(block_devices, data_device_name, 128 * 1024, 1); + if (!builder) { + LOG(ERROR) << "Error creating metadata builder"; + return nullptr; + } + builder->IgnoreSlotSuffixing(); + + for (const auto& [name, image] : partitions_) { + uint32_t flags = LP_PARTITION_ATTR_NONE; + if (name == "system_gsi") { + flags |= LP_PARTITION_ATTR_READONLY; + } + Partition* partition = builder->AddPartition(name, flags); + if (!partition) { + LOG(ERROR) << "Error adding " << name << " to partition table"; + return nullptr; + } + if (!AddPartitionFiemap(builder.get(), partition, image, data_device_name)) { + return nullptr; + } + } + + auto metadata = builder->Export(); + if (!metadata) { + LOG(ERROR) << "Error exporting partition table"; + return nullptr; + } + return metadata; +} + +bool GsiInstaller::CreateMetadataFile() { + if (!WriteToImageFile(kGsiLpMetadataFile, *metadata_.get())) { + LOG(ERROR) << "Error writing GSI partition table image"; + return false; + } + return true; +} + +bool GsiInstaller::FormatUserdata() { + auto writer = OpenPartition("userdata_gsi"); + if (!writer) { + return false; + } + + // libcutils checks the first 4K, no matter the block size. + std::string zeroes(4096, 0); + if (!writer->Write(zeroes.data(), zeroes.size())) { + PLOG(ERROR) << "write userdata_gsi"; + return false; + } + return true; +} + +bool GsiInstaller::AddPartitionFiemap(MetadataBuilder* builder, Partition* partition, + const Image& image, const std::string& block_device) { + uint64_t sectors_needed = image.actual_size / LP_SECTOR_SIZE; + for (const auto& extent : image.writer->extents()) { + // :TODO: block size check for length, not sector size + if (extent.fe_length % LP_SECTOR_SIZE != 0) { + LOG(ERROR) << "Extent is not sector-aligned: " << extent.fe_length; + return false; + } + if (extent.fe_physical % LP_SECTOR_SIZE != 0) { + LOG(ERROR) << "Extent physical sector is not sector-aligned: " << extent.fe_physical; + return false; + } + + uint64_t num_sectors = + std::min(static_cast<uint64_t>(extent.fe_length / LP_SECTOR_SIZE), sectors_needed); + if (!num_sectors || !sectors_needed) { + // This should never happen, but we include it just in case. It would + // indicate that the last filesystem block had multiple extents. + LOG(WARNING) << "FiemapWriter allocated extra blocks"; + break; + } + + uint64_t physical_sector = extent.fe_physical / LP_SECTOR_SIZE; + if (!builder->AddLinearExtent(partition, block_device, num_sectors, physical_sector)) { + LOG(ERROR) << "Could not add extent to lp metadata"; + return false; + } + + sectors_needed -= num_sectors; + } + return true; +} + +static uint64_t GetPartitionSize(const LpMetadata& metadata, const LpMetadataPartition& partition) { + uint64_t total = 0; + for (size_t i = 0; i < partition.num_extents; i++) { + const auto& extent = metadata.extents[partition.first_extent_index + i]; + if (extent.target_type != LP_TARGET_TYPE_LINEAR) { + LOG(ERROR) << "non-linear extent detected"; + return 0; + } + total += extent.num_sectors * LP_SECTOR_SIZE; + } + return total; +} + +static uint64_t GetPartitionSize(const LpMetadata& metadata, const std::string& name) { + for (const auto& partition : metadata.partitions) { + if (GetPartitionName(partition) == name) { + return GetPartitionSize(metadata, partition); + } + } + return 0; +} + +int GsiInstaller::GetExistingImage(const LpMetadata& metadata, const std::string& name, + Image* image) { + int error; + std::string path = GetImagePath(name); + auto writer = CreateFiemapWriter(path.c_str(), 0, &error); + if (!writer) { + return error; + } + + // Even after recovering the FIEMAP, we also need to know the exact intended + // size of the image, since FiemapWriter may have extended the final block. + uint64_t actual_size = GetPartitionSize(metadata, name); + if (!actual_size) { + LOG(ERROR) << "Could not determine the pre-existing size of " << name; + return IGsiService::INSTALL_ERROR_GENERIC; + } + image->writer = std::move(writer); + image->actual_size = actual_size; + return IGsiService::INSTALL_OK; +} + +int GsiInstaller::SetGsiBootable(bool one_shot) { + if (gsi_bytes_written_ != gsi_size_) { + // We cannot boot if the image is incomplete. + LOG(ERROR) << "image incomplete; expected " << gsi_size_ << " bytes, waiting for " + << (gsi_size_ - gsi_bytes_written_) << " bytes"; + return IGsiService::INSTALL_ERROR_GENERIC; + } + + if (!system_writer_->Flush()) { + return IGsiService::INSTALL_ERROR_GENERIC; + } + + // If files moved (are no longer pinned), the metadata file will be invalid. + for (const auto& [name, image] : partitions_) { + if (!image.writer->HasPinnedExtents()) { + LOG(ERROR) << name << " no longer has pinned extents"; + return IGsiService::INSTALL_ERROR_GENERIC; + } + } + + // Remember the installation directory. + if (!android::base::WriteStringToFile(install_dir_, kGsiInstallDirFile)) { + PLOG(ERROR) << "write failed: " << kGsiInstallDirFile; + return IGsiService::INSTALL_ERROR_GENERIC; + } + + // Note: create the install status file last, since this is the actual boot + // indicator. + if (!CreateMetadataFile() || !SetBootMode(one_shot) || !CreateInstallStatusFile()) { + return IGsiService::INSTALL_ERROR_GENERIC; + } + + succeeded_ = true; + return IGsiService::INSTALL_OK; +} + +int GsiInstaller::ReenableGsi(bool one_shot) { + if (int error = DetermineReadWriteMethod()) { + return error; + } + + if (IsGsiRunning()) { + if (!SetBootMode(one_shot) || !CreateInstallStatusFile()) { + return IGsiService::INSTALL_ERROR_GENERIC; + } + return IGsiService::INSTALL_OK; + } + + // Note: this metadata is only used to recover the original partition sizes. + // We do not trust the extent information, which will get rebuilt later. + auto old_metadata = ReadFromImageFile(kGsiLpMetadataFile); + if (!old_metadata) { + LOG(ERROR) << "GSI install is incomplete"; + return IGsiService::INSTALL_ERROR_GENERIC; + } + + // Recover parition information. + Image userdata_image; + if (int error = GetExistingImage(*old_metadata.get(), "userdata_gsi", &userdata_image)) { + return error; + } + partitions_.emplace(std::make_pair("userdata_gsi", std::move(userdata_image))); + + Image system_image; + if (int error = GetExistingImage(*old_metadata.get(), "system_gsi", &system_image)) { + return error; + } + partitions_.emplace(std::make_pair("system_gsi", std::move(system_image))); + + metadata_ = CreateMetadata(); + if (!metadata_) { + return IGsiService::INSTALL_ERROR_GENERIC; + } + + if (!CreateMetadataFile() || !SetBootMode(one_shot) || !CreateInstallStatusFile()) { + return IGsiService::INSTALL_ERROR_GENERIC; + } + + succeeded_ = true; + return IGsiService::INSTALL_OK; +} + +} // namespace gsi +} // namespace android diff --git a/gsi_installer.h b/gsi_installer.h new file mode 100644 index 0000000..51c2bd6 --- /dev/null +++ b/gsi_installer.h @@ -0,0 +1,124 @@ +/* + * 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. + */ +#pragma once + +#include <stdint.h> + +#include <memory> +#include <string> + +#include <android/gsi/IGsiService.h> +#include <libfiemap_writer/split_fiemap_writer.h> +#include <liblp/builder.h> + +namespace android { +namespace gsi { + +class GsiService; + +class GsiInstaller final { + public: + // Constructor for a new GSI installation. + GsiInstaller(GsiService* service, const GsiInstallParams& params); + // Constructor for re-enabling a previous GSI installation. + GsiInstaller(GsiService* service, const std::string& install_dir); + ~GsiInstaller(); + + // Methods for a clean GSI install. + int StartInstall(); + bool CommitGsiChunk(int stream_fd, int64_t bytes); + bool CommitGsiChunk(const void* data, size_t bytes); + int SetGsiBootable(bool one_shot); + + // Methods for re-enabling an existing install. + int ReenableGsi(bool one_shot); + + // Clean up install state if gsid crashed and restarted. + static void PostInstallCleanup(); + + // This helper class will redirect writes to either a SplitFiemap or + // device-mapper. + class WriteHelper { + public: + virtual ~WriteHelper(){}; + virtual bool Write(const void* data, uint64_t bytes) = 0; + virtual bool Flush() = 0; + + WriteHelper() = default; + WriteHelper(const WriteHelper&) = delete; + WriteHelper& operator=(const WriteHelper&) = delete; + WriteHelper& operator=(WriteHelper&&) = delete; + WriteHelper(WriteHelper&&) = delete; + }; + + const std::string& install_dir() const { return install_dir_; } + uint64_t userdata_size() const { return userdata_size_; } + + private: + using MetadataBuilder = android::fs_mgr::MetadataBuilder; + using LpMetadata = android::fs_mgr::LpMetadata; + using SplitFiemap = android::fiemap_writer::SplitFiemap; + + // The image file may be larger than the requested size, due to alignment, + // so we must track the requested size as well. + struct Image { + std::unique_ptr<SplitFiemap> writer; + uint64_t actual_size; + }; + + int PerformSanityChecks(); + int PreallocateFiles(); + int PreallocateUserdata(); + int PreallocateSystem(); + int DetermineReadWriteMethod(); + bool FormatUserdata(); + bool AddPartitionFiemap(MetadataBuilder* builder, android::fs_mgr::Partition* partition, + const Image& image, const std::string& block_device); + std::unique_ptr<LpMetadata> CreateMetadata(); + std::unique_ptr<SplitFiemap> CreateFiemapWriter(const std::string& path, uint64_t size, + int* error); + std::unique_ptr<WriteHelper> OpenPartition(const std::string& name); + int GetExistingImage(const LpMetadata& metadata, const std::string& name, Image* image); + bool CreateInstallStatusFile(); + bool CreateMetadataFile(); + bool SetBootMode(bool one_shot); + std::string GetImagePath(const std::string& name); + + GsiService* service_; + + std::string install_dir_; + std::string userdata_gsi_path_; + std::string system_gsi_path_; + uint64_t userdata_block_size_ = 0; + uint64_t system_block_size_ = 0; + uint64_t gsi_size_ = 0; + uint64_t userdata_size_ = 0; + bool can_use_devicemapper_ = false; + bool wipe_userdata_ = false; + bool wipe_userdata_on_failure_ = false; + // Remaining data we're waiting to receive for the GSI image. + uint64_t gsi_bytes_written_ = 0; + bool succeeded_ = false; + + std::unique_ptr<WriteHelper> system_writer_; + + // This is used to track which GSI partitions have been created. + std::map<std::string, Image> partitions_; + std::unique_ptr<LpMetadata> metadata_; +}; + +} // namespace gsi +} // namespace android diff --git a/gsi_service.cpp b/gsi_service.cpp index 7b30d0b..24a741b 100644 --- a/gsi_service.cpp +++ b/gsi_service.cpp @@ -20,7 +20,6 @@ #include <linux/fs.h> #include <sys/ioctl.h> #include <sys/stat.h> -#include <sys/statvfs.h> #include <sys/types.h> #include <sys/vfs.h> #include <unistd.h> @@ -35,11 +34,6 @@ #include <android-base/strings.h> #include <android/gsi/IGsiService.h> #include <fs_mgr.h> -#include <fs_mgr_dm_linear.h> -#include <fstab/fstab.h> -#include <libdm/dm.h> -#include <libfiemap_writer/fiemap_writer.h> -#include <logwrap/logwrap.h> #include <private/android_filesystem_config.h> #include "file_paths.h" @@ -49,25 +43,11 @@ namespace android { namespace gsi { using namespace std::literals; -using namespace android::dm; using namespace android::fs_mgr; using namespace android::fiemap_writer; using android::base::StringPrintf; using android::base::unique_fd; -static constexpr char kGsiDataFolder[] = "/data/gsi/"; -static constexpr char kUserdataDevice[] = "/dev/block/by-name/userdata"; - -// The default size of userdata.img for GSI. -// We are looking for /data to have atleast 40% free space -static constexpr uint32_t kMinimumFreeSpaceThreshold = 40; -// We determine the fragmentation by making sure the files -// we create don't have more than 16 extents. -static constexpr uint32_t kMaximumExtents = 512; -// Default userdata image size. -static constexpr int64_t kDefaultUserdataSize = int64_t(8) * 1024 * 1024 * 1024; -static constexpr std::chrono::milliseconds kDmTimeout = 5000ms; - void GsiService::Register() { auto ret = android::BinderService<GsiService>::publish(); if (ret != android::OK) { @@ -77,11 +57,10 @@ void GsiService::Register() { GsiService::GsiService() { progress_ = {}; + GsiInstaller::PostInstallCleanup(); } -GsiService::~GsiService() { - PostInstallCleanup(); -} +GsiService::~GsiService() {} #define ENFORCE_SYSTEM \ do { \ @@ -110,7 +89,7 @@ binder::Status GsiService::beginGsiInstall(const GsiInstallParams& given_params, std::lock_guard<std::mutex> guard(main_lock_); // Make sure any interrupted installations are cleaned up. - PostInstallCleanup(); + installer_ = nullptr; // Do some precursor validation on the arguments before diving into the // install process. @@ -120,16 +99,12 @@ binder::Status GsiService::beginGsiInstall(const GsiInstallParams& given_params, return binder::Status::ok(); } - int status = StartInstall(params); + installer_ = std::make_unique<GsiInstaller>(this, params); + int status = installer_->StartInstall(); if (status != INSTALL_OK) { - // Perform local cleanup and delete any lingering files. - PostInstallCleanup(); - RemoveGsiFiles(params.installDir, wipe_userdata_on_failure_); + installer_ = nullptr; } *_aidl_return = status; - - // Clear the progress indicator. - UpdateProgress(STATUS_NO_OPERATION, 0); return binder::Status::ok(); } @@ -138,10 +113,12 @@ binder::Status GsiService::commitGsiChunkFromStream(const android::os::ParcelFil ENFORCE_SYSTEM; std::lock_guard<std::mutex> guard(main_lock_); - *_aidl_return = CommitGsiChunk(stream.get(), bytes); + if (!installer_) { + *_aidl_return = false; + return binder::Status::ok(); + } - // Clear the progress indicator. - UpdateProgress(STATUS_NO_OPERATION, 0); + *_aidl_return = installer_->CommitGsiChunk(stream.get(), bytes); return binder::Status::ok(); } @@ -178,19 +155,21 @@ binder::Status GsiService::commitGsiChunkFromMemory(const std::vector<uint8_t>& ENFORCE_SYSTEM; std::lock_guard<std::mutex> guard(main_lock_); - *_aidl_return = CommitGsiChunk(bytes.data(), bytes.size()); + if (!installer_) { + *_aidl_return = false; + return binder::Status::ok(); + } + + *_aidl_return = installer_->CommitGsiChunk(bytes.data(), bytes.size()); return binder::Status::ok(); } binder::Status GsiService::setGsiBootable(bool one_shot, int* _aidl_return) { std::lock_guard<std::mutex> guard(main_lock_); - if (installing_) { + if (installer_) { ENFORCE_SYSTEM; - int error = SetGsiBootable(one_shot); - PostInstallCleanup(); - if (error) { - RemoveGsiFiles(install_dir_, wipe_userdata_on_failure_); + if (int error = installer_->SetGsiBootable(one_shot)) { *_aidl_return = error; } else { *_aidl_return = INSTALL_OK; @@ -198,9 +177,9 @@ binder::Status GsiService::setGsiBootable(bool one_shot, int* _aidl_return) { } else { ENFORCE_SYSTEM_OR_SHELL; *_aidl_return = ReenableGsi(one_shot); - PostInstallCleanup(); } + installer_ = nullptr; return binder::Status::ok(); } @@ -222,9 +201,8 @@ binder::Status GsiService::removeGsiInstall(bool* _aidl_return) { // Just in case an install was left hanging. std::string install_dir; - if (installing_) { - install_dir = install_dir_; - PostInstallCleanup(); + if (installer_) { + install_dir = installer_->install_dir(); } else { install_dir = GetInstalledImageDir(); } @@ -266,7 +244,7 @@ binder::Status GsiService::isGsiInstallInProgress(bool* _aidl_return) { ENFORCE_SYSTEM_OR_SHELL; std::lock_guard<std::mutex> guard(main_lock_); - *_aidl_return = installing_; + *_aidl_return = !!installer_; return binder::Status::ok(); } @@ -276,10 +254,7 @@ binder::Status GsiService::cancelGsiInstall(bool* _aidl_return) { std::lock_guard<std::mutex> guard(main_lock_); should_abort_ = false; - if (installing_) { - PostInstallCleanup(); - RemoveGsiFiles(install_dir_, wipe_userdata_on_failure_); - } + installer_ = nullptr; *_aidl_return = true; return binder::Status::ok(); @@ -330,9 +305,9 @@ binder::Status GsiService::getUserdataImageSize(int64_t* _aidl_return) { *_aidl_return = -1; - if (installing_) { + if (installer_) { // Size has already been computed. - *_aidl_return = userdata_size_; + *_aidl_return = installer_->userdata_size(); } else if (IsGsiRunning()) { // :TODO: libdm unique_fd fd(open(kUserdataDevice, O_RDONLY | O_NOFOLLOW | O_CLOEXEC)); @@ -391,22 +366,6 @@ binder::Status GsiService::CheckUid(AccessLevel level) { return binder::Status::fromExceptionCode(binder::Status::EX_SECURITY, String8(message.c_str())); } -void GsiService::PostInstallCleanup() { - // This must be closed before unmapping partitions. - system_writer_ = nullptr; - - const auto& dm = DeviceMapper::Instance(); - if (dm.GetState("userdata_gsi") != DmDeviceState::INVALID) { - DestroyLogicalPartition("userdata_gsi", kDmTimeout); - } - if (dm.GetState("system_gsi") != DmDeviceState::INVALID) { - DestroyLogicalPartition("system_gsi", kDmTimeout); - } - - installing_ = false; - partitions_.clear(); -} - static bool IsExternalStoragePath(const std::string& path) { if (!android::base::StartsWith(path, "/mnt/media_rw/")) { return false; @@ -476,69 +435,6 @@ int GsiService::ValidateInstallParams(GsiInstallParams* params) { return INSTALL_OK; } -int GsiService::StartInstall(const GsiInstallParams& params) { - installing_ = true; - userdata_block_size_ = 0; - system_block_size_ = 0; - gsi_size_ = params.gsiSize; - userdata_size_ = (params.userdataSize) ? params.userdataSize : kDefaultUserdataSize; - wipe_userdata_ = params.wipeUserdata; - can_use_devicemapper_ = false; - gsi_bytes_written_ = 0; - install_dir_ = params.installDir; - - userdata_gsi_path_ = GetImagePath(install_dir_, "userdata_gsi"); - system_gsi_path_ = GetImagePath(install_dir_, "system_gsi"); - - // Only rm userdata_gsi if one didn't already exist. - wipe_userdata_on_failure_ = wipe_userdata_ || access(userdata_gsi_path_.c_str(), F_OK); - - if (int status = PerformSanityChecks()) { - return status; - } - if (int status = PreallocateFiles()) { - return status; - } - if (int status = DetermineReadWriteMethod()) { - return status; - } - if (!FormatUserdata()) { - return INSTALL_ERROR_GENERIC; - } - - // Map system_gsi so we can write to it. - system_writer_ = OpenPartition("system_gsi"); - if (!system_writer_) { - return INSTALL_ERROR_GENERIC; - } - return INSTALL_OK; -} - -int GsiService::DetermineReadWriteMethod() { - // 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). - std::string block_device; - if (!FiemapWriter::GetBlockDeviceForFile(system_gsi_path_.c_str(), &block_device, - &can_use_devicemapper_)) { - return INSTALL_ERROR_GENERIC; - } - if (install_dir_ != kGsiDataFolder && can_use_devicemapper_) { - // Never use device-mapper on external media. We don't support adopted - // storage yet, and accidentally using device-mapper could be dangerous - // as we hardcode the userdata device as backing storage. - LOG(ERROR) << "unexpected device-mapper node used to mount external media"; - return INSTALL_ERROR_GENERIC; - } - return INSTALL_OK; -} - std::string GsiService::GetImagePath(const std::string& image_dir, const std::string& name) { std::string dir = image_dir; if (!android::base::EndsWith(dir, "/")) { @@ -561,309 +457,6 @@ std::string GsiService::GetInstalledImagePath(const std::string& name) { return GetImagePath(GetInstalledImageDir(), name); } -int GsiService::PerformSanityChecks() { - if (gsi_size_ < 0) { - LOG(ERROR) << "image size " << gsi_size_ << " is negative"; - return INSTALL_ERROR_GENERIC; - } - if (android::gsi::IsGsiRunning()) { - LOG(ERROR) << "cannot install gsi inside a live gsi"; - return INSTALL_ERROR_GENERIC; - } - - struct statvfs sb; - if (statvfs(install_dir_.c_str(), &sb)) { - PLOG(ERROR) << "failed to read file system stats"; - return INSTALL_ERROR_GENERIC; - } - - // This is the same as android::vold::GetFreebytes() but we also - // need the total file system size so we open code it here. - uint64_t free_space = 1ULL * sb.f_bavail * sb.f_frsize; - uint64_t fs_size = sb.f_blocks * sb.f_frsize; - if (free_space <= (gsi_size_ + userdata_size_)) { - LOG(ERROR) << "not enough free space (only " << free_space << " bytes available)"; - return INSTALL_ERROR_NO_SPACE; - } - // We are asking for 40% of the /data to be empty. - // TODO: may be not hard code it like this - double free_space_percent = ((1.0 * free_space) / fs_size) * 100; - if (free_space_percent < kMinimumFreeSpaceThreshold) { - LOG(ERROR) << "free space " << static_cast<uint64_t>(free_space_percent) - << "% is below the minimum threshold of " << kMinimumFreeSpaceThreshold << "%"; - return INSTALL_ERROR_FILE_SYSTEM_CLUTTERED; - } - return INSTALL_OK; -} - -int GsiService::PreallocateFiles() { - if (wipe_userdata_) { - SplitFiemap::RemoveSplitFiles(userdata_gsi_path_); - } - SplitFiemap::RemoveSplitFiles(system_gsi_path_); - - // TODO: trigger GC from fiemap writer. - - // Create fallocated files. - if (int status = PreallocateUserdata()) { - return status; - } - if (int status = PreallocateSystem()) { - return status; - } - - // Save the extent information in liblp. - metadata_ = CreateMetadata(); - if (!metadata_) { - return INSTALL_ERROR_GENERIC; - } - - UpdateProgress(STATUS_COMPLETE, 0); - return INSTALL_OK; -} - -int GsiService::PreallocateUserdata() { - int error; - std::unique_ptr<SplitFiemap> userdata_image; - if (wipe_userdata_ || access(userdata_gsi_path_.c_str(), F_OK)) { - StartAsyncOperation("create userdata", userdata_size_); - userdata_image = CreateFiemapWriter(userdata_gsi_path_, userdata_size_, &error); - if (!userdata_image) { - LOG(ERROR) << "Could not create userdata image: " << userdata_gsi_path_; - return error; - } - // Signal that we need to reformat userdata. - wipe_userdata_ = true; - } else { - userdata_image = CreateFiemapWriter(userdata_gsi_path_, 0, &error); - if (!userdata_image) { - LOG(ERROR) << "Could not open userdata image: " << userdata_gsi_path_; - return error; - } - if (userdata_size_ && userdata_image->size() < userdata_size_) { - // :TODO: need to fallocate more blocks and resizefs. - } - userdata_size_ = userdata_image->size(); - } - - userdata_block_size_ = userdata_image->block_size(); - - Image image = { - .writer = std::move(userdata_image), - .actual_size = userdata_size_, - }; - partitions_.emplace(std::make_pair("userdata_gsi", std::move(image))); - return INSTALL_OK; -} - -int GsiService::PreallocateSystem() { - StartAsyncOperation("create system", gsi_size_); - - int error; - auto system_image = CreateFiemapWriter(system_gsi_path_, gsi_size_, &error); - if (!system_image) { - return error; - } - - system_block_size_ = system_image->block_size(); - - Image image = { - .writer = std::move(system_image), - .actual_size = gsi_size_, - }; - partitions_.emplace(std::make_pair("system_gsi", std::move(image))); - return INSTALL_OK; -} - -std::unique_ptr<SplitFiemap> GsiService::CreateFiemapWriter(const std::string& path, uint64_t size, - int* error) { - bool create = (size != 0); - - std::function<bool(uint64_t, uint64_t)> progress; - if (create) { - // TODO: allow cancelling inside cancelGsiInstall. - progress = [this](uint64_t bytes, uint64_t /* total */) -> bool { - UpdateProgress(STATUS_WORKING, bytes); - if (should_abort_) return false; - return true; - }; - } - - std::unique_ptr<SplitFiemap> file; - if (!size) { - file = SplitFiemap::Open(path); - } else { - file = SplitFiemap::Create(path, size, 0, std::move(progress)); - } - if (!file) { - LOG(ERROR) << "failed to create or open " << path; - *error = INSTALL_ERROR_GENERIC; - return nullptr; - } - - uint64_t extents = file->extents().size(); - if (extents > kMaximumExtents) { - LOG(ERROR) << "file " << path << " has too many extents: " << extents; - *error = INSTALL_ERROR_FILE_SYSTEM_CLUTTERED; - return nullptr; - } - return file; -} - -// Write data through an fd. -class FdWriter final : public GsiService::WriteHelper { - public: - FdWriter(const std::string& path, unique_fd&& fd) : path_(path), fd_(std::move(fd)) {} - - bool Write(const void* data, uint64_t bytes) override { - return android::base::WriteFully(fd_, data, bytes); - } - bool Flush() override { - if (fsync(fd_)) { - PLOG(ERROR) << "fsync failed: " << path_; - return false; - } - return true; - } - - private: - std::string path_; - unique_fd fd_; -}; - -// Write data through a SplitFiemap. -class SplitFiemapWriter final : public GsiService::WriteHelper { - public: - explicit SplitFiemapWriter(SplitFiemap* writer) : writer_(writer) {} - - bool Write(const void* data, uint64_t bytes) override { return writer_->Write(data, bytes); } - bool Flush() override { return writer_->Flush(); } - - private: - SplitFiemap* writer_; -}; - -std::unique_ptr<GsiService::WriteHelper> GsiService::OpenPartition(const std::string& name) { - if (can_use_devicemapper_) { - std::string path; - if (!CreateLogicalPartition(kUserdataDevice, *metadata_.get(), name, true, kDmTimeout, - &path)) { - LOG(ERROR) << "Error creating device-mapper node for " << name; - return {}; - } - - static const int kOpenFlags = O_RDWR | O_NOFOLLOW | O_CLOEXEC; - unique_fd fd(open(path.c_str(), kOpenFlags)); - if (fd < 0) { - PLOG(ERROR) << "could not open " << path; - } - return std::make_unique<FdWriter>(GetImagePath(install_dir_, name), std::move(fd)); - } - - auto iter = partitions_.find(name); - if (iter == partitions_.end()) { - LOG(ERROR) << "could not find partition " << name; - return {}; - } - return std::make_unique<SplitFiemapWriter>(iter->second.writer.get()); -} - -bool GsiService::CommitGsiChunk(int stream_fd, int64_t bytes) { - StartAsyncOperation("write gsi", gsi_size_); - - if (bytes < 0) { - LOG(ERROR) << "chunk size " << bytes << " is negative"; - return false; - } - - auto buffer = std::make_unique<char[]>(system_block_size_); - - int progress = -1; - uint64_t remaining = bytes; - while (remaining) { - // :TODO: check file pin status! - size_t max_to_read = std::min(system_block_size_, remaining); - ssize_t rv = TEMP_FAILURE_RETRY(read(stream_fd, buffer.get(), max_to_read)); - if (rv < 0) { - PLOG(ERROR) << "read gsi chunk"; - return false; - } - if (rv == 0) { - LOG(ERROR) << "no bytes left in stream"; - return false; - } - if (!CommitGsiChunk(buffer.get(), rv)) { - return false; - } - CHECK(static_cast<uint64_t>(rv) <= remaining); - remaining -= rv; - - // Only update the progress when the % (or permille, in this case) - // significantly changes. - int new_progress = ((gsi_size_ - remaining) * 1000) / gsi_size_; - if (new_progress != progress) { - UpdateProgress(STATUS_WORKING, gsi_size_ - remaining); - } - } - - UpdateProgress(STATUS_COMPLETE, gsi_size_); - return true; -} - -bool GsiService::CommitGsiChunk(const void* data, size_t bytes) { - if (!installing_) { - LOG(ERROR) << "no gsi installation in progress"; - return false; - } - if (static_cast<uint64_t>(bytes) > gsi_size_ - gsi_bytes_written_) { - // We cannot write past the end of the image file. - LOG(ERROR) << "chunk size " << bytes << " exceeds remaining image size (" << gsi_size_ - << " expected, " << gsi_bytes_written_ << " written)"; - return false; - } - - if (!system_writer_->Write(data, bytes)) { - PLOG(ERROR) << "write failed"; - return false; - } - gsi_bytes_written_ += bytes; - return true; -} - -int GsiService::SetGsiBootable(bool one_shot) { - if (gsi_bytes_written_ != gsi_size_) { - // We cannot boot if the image is incomplete. - LOG(ERROR) << "image incomplete; expected " << gsi_size_ << " bytes, waiting for " - << (gsi_size_ - gsi_bytes_written_) << " bytes"; - return INSTALL_ERROR_GENERIC; - } - - if (!system_writer_->Flush()) { - return INSTALL_ERROR_GENERIC; - } - - // If files moved (are no longer pinned), the metadata file will be invalid. - for (const auto& [name, image] : partitions_) { - if (!image.writer->HasPinnedExtents()) { - LOG(ERROR) << name << " no longer has pinned extents"; - return INSTALL_ERROR_GENERIC; - } - } - - // Remember the installation directory. - if (!android::base::WriteStringToFile(install_dir_, kGsiInstallDirFile)) { - PLOG(ERROR) << "write failed: " << kGsiInstallDirFile; - return INSTALL_ERROR_GENERIC; - } - - // Note: create the install status file last, since this is the actual boot - // indicator. - if (!CreateMetadataFile() || !SetBootMode(one_shot) || !CreateInstallStatusFile()) { - return INSTALL_ERROR_GENERIC; - } - return INSTALL_OK; -} - int GsiService::ReenableGsi(bool one_shot) { if (!android::gsi::IsGsiInstalled()) { LOG(ERROR) << "no gsi installed - cannot re-enable"; @@ -880,95 +473,8 @@ int GsiService::ReenableGsi(bool one_shot) { return INSTALL_ERROR_GENERIC; } - if (IsGsiRunning()) { - if (!SetBootMode(one_shot) || !CreateInstallStatusFile()) { - return INSTALL_ERROR_GENERIC; - } - return INSTALL_OK; - } - // Note: this metadata is only used to recover the original partition sizes. - // We do not trust the extent information, which will get rebuilt later. - auto old_metadata = ReadFromImageFile(kGsiLpMetadataFile); - if (!old_metadata) { - LOG(ERROR) << "GSI install is incomplete"; - return INSTALL_ERROR_GENERIC; - } - - // Set up enough installer state so that we can use various helper - // methods. - // - // TODO(dvander) Extract all of the installer state into a separate - // class so this is more manageable. - install_dir_ = GetInstalledImageDir(); - system_gsi_path_ = GetImagePath(install_dir_, "system_gsi"); - if (int error = DetermineReadWriteMethod()) { - return error; - } - - // Recover parition information. - Image userdata_image; - if (int error = GetExistingImage(*old_metadata.get(), "userdata_gsi", &userdata_image)) { - return error; - } - partitions_.emplace(std::make_pair("userdata_gsi", std::move(userdata_image))); - - Image system_image; - if (int error = GetExistingImage(*old_metadata.get(), "system_gsi", &system_image)) { - return error; - } - partitions_.emplace(std::make_pair("system_gsi", std::move(system_image))); - - metadata_ = CreateMetadata(); - if (!metadata_) { - return INSTALL_ERROR_GENERIC; - } - if (!CreateMetadataFile() || !SetBootMode(one_shot) || !CreateInstallStatusFile()) { - return INSTALL_ERROR_GENERIC; - } - return INSTALL_OK; -} - -static uint64_t GetPartitionSize(const LpMetadata& metadata, const LpMetadataPartition& partition) { - uint64_t total = 0; - for (size_t i = 0; i < partition.num_extents; i++) { - const auto& extent = metadata.extents[partition.first_extent_index + i]; - if (extent.target_type != LP_TARGET_TYPE_LINEAR) { - LOG(ERROR) << "non-linear extent detected"; - return 0; - } - total += extent.num_sectors * LP_SECTOR_SIZE; - } - return total; -} - -static uint64_t GetPartitionSize(const LpMetadata& metadata, const std::string& name) { - for (const auto& partition : metadata.partitions) { - if (GetPartitionName(partition) == name) { - return GetPartitionSize(metadata, partition); - } - } - return 0; -} - -int GsiService::GetExistingImage(const LpMetadata& metadata, const std::string& name, - Image* image) { - int error; - std::string path = GetInstalledImagePath(name); - auto writer = CreateFiemapWriter(path.c_str(), 0, &error); - if (!writer) { - return error; - } - - // Even after recovering the FIEMAP, we also need to know the exact intended - // size of the image, since FiemapWriter may have extended the final block. - uint64_t actual_size = GetPartitionSize(metadata, name); - if (!actual_size) { - LOG(ERROR) << "Could not determine the pre-existing size of " << name; - return INSTALL_ERROR_GENERIC; - } - image->writer = std::move(writer); - image->actual_size = actual_size; - return INSTALL_OK; + installer_ = std::make_unique<GsiInstaller>(this, GetInstalledImageDir()); + return installer_->ReenableGsi(one_shot); } bool GsiService::RemoveGsiFiles(const std::string& install_dir, bool wipeUserdata) { @@ -1004,7 +510,7 @@ bool GsiService::DisableGsiInstall() { LOG(ERROR) << "cannot disable gsi install - no install detected"; return false; } - if (installing_) { + if (installer_) { LOG(ERROR) << "cannot disable gsi during GSI installation"; return false; } @@ -1015,135 +521,6 @@ bool GsiService::DisableGsiInstall() { return true; } -std::unique_ptr<LpMetadata> GsiService::CreateMetadata() { - std::string data_device_path; - if (install_dir_ == kGsiDataFolder && !access(kUserdataDevice, F_OK)) { - data_device_path = kUserdataDevice; - } else { - auto writer = partitions_["system_gsi"].writer.get(); - data_device_path = writer->bdev_path(); - } - auto data_device_name = android::base::Basename(data_device_path); - - PartitionOpener opener; - BlockDeviceInfo data_device_info; - if (!opener.GetInfo(data_device_path, &data_device_info)) { - LOG(ERROR) << "Error reading userdata partition"; - return nullptr; - } - - std::vector<BlockDeviceInfo> block_devices = {data_device_info}; - auto builder = MetadataBuilder::New(block_devices, data_device_name, 128 * 1024, 1); - if (!builder) { - LOG(ERROR) << "Error creating metadata builder"; - return nullptr; - } - builder->IgnoreSlotSuffixing(); - - for (const auto& [name, image] : partitions_) { - uint32_t flags = LP_PARTITION_ATTR_NONE; - if (name == "system_gsi") { - flags |= LP_PARTITION_ATTR_READONLY; - } - Partition* partition = builder->AddPartition(name, flags); - if (!partition) { - LOG(ERROR) << "Error adding " << name << " to partition table"; - return nullptr; - } - if (!AddPartitionFiemap(builder.get(), partition, image, data_device_name)) { - return nullptr; - } - } - - auto metadata = builder->Export(); - if (!metadata) { - LOG(ERROR) << "Error exporting partition table"; - return nullptr; - } - return metadata; -} - -bool GsiService::CreateMetadataFile() { - if (!WriteToImageFile(kGsiLpMetadataFile, *metadata_.get())) { - LOG(ERROR) << "Error writing GSI partition table image"; - return false; - } - return true; -} - -bool GsiService::FormatUserdata() { - auto writer = OpenPartition("userdata_gsi"); - if (!writer) { - return false; - } - - // libcutils checks the first 4K, no matter the block size. - std::string zeroes(4096, 0); - if (!writer->Write(zeroes.data(), zeroes.size())) { - PLOG(ERROR) << "write userdata_gsi"; - return false; - } - return true; -} - -bool GsiService::AddPartitionFiemap(MetadataBuilder* builder, Partition* partition, - const Image& image, const std::string& block_device) { - uint64_t sectors_needed = image.actual_size / LP_SECTOR_SIZE; - for (const auto& extent : image.writer->extents()) { - // :TODO: block size check for length, not sector size - if (extent.fe_length % LP_SECTOR_SIZE != 0) { - LOG(ERROR) << "Extent is not sector-aligned: " << extent.fe_length; - return false; - } - if (extent.fe_physical % LP_SECTOR_SIZE != 0) { - LOG(ERROR) << "Extent physical sector is not sector-aligned: " << extent.fe_physical; - return false; - } - - uint64_t num_sectors = - std::min(static_cast<uint64_t>(extent.fe_length / LP_SECTOR_SIZE), sectors_needed); - if (!num_sectors || !sectors_needed) { - // This should never happen, but we include it just in case. It would - // indicate that the last filesystem block had multiple extents. - LOG(WARNING) << "FiemapWriter allocated extra blocks"; - break; - } - - uint64_t physical_sector = extent.fe_physical / LP_SECTOR_SIZE; - if (!builder->AddLinearExtent(partition, block_device, num_sectors, physical_sector)) { - LOG(ERROR) << "Could not add extent to lp metadata"; - return false; - } - - sectors_needed -= num_sectors; - } - return true; -} - -bool GsiService::SetBootMode(bool one_shot) { - if (one_shot) { - if (!android::base::WriteStringToFile("1", kGsiOneShotBootFile)) { - PLOG(ERROR) << "write " << kGsiOneShotBootFile; - return false; - } - } else if (!access(kGsiOneShotBootFile, F_OK)) { - std::string error; - if (!android::base::RemoveFileIfExists(kGsiOneShotBootFile, &error)) { - LOG(ERROR) << error; - return false; - } - } - return true; -} - -bool GsiService::CreateInstallStatusFile() { - if (!android::base::WriteStringToFile("0", kGsiInstallStatusFile)) { - PLOG(ERROR) << "write " << kGsiInstallStatusFile; - return false; - } - return true; -} - void GsiService::RunStartupTasks() { if (!IsGsiInstalled()) { return; diff --git a/gsi_service.h b/gsi_service.h index 7a30d88..ddc6fc4 100644 --- a/gsi_service.h +++ b/gsi_service.h @@ -29,6 +29,8 @@ #include <liblp/builder.h> #include "libgsi/libgsi.h" +#include "gsi_installer.h" + namespace android { namespace gsi { @@ -59,24 +61,19 @@ class GsiService : public BinderService<GsiService>, public BnGsiService { binder::Status getGsiBootStatus(int* _aidl_return) override; binder::Status getInstalledGsiImageDir(std::string* _aidl_return) override; + // This is in GsiService, rather than GsiInstaller, since we need to access + // it outside of the main lock which protects the unique_ptr. + void StartAsyncOperation(const std::string& step, int64_t total_bytes); + void UpdateProgress(int status, int64_t bytes_processed); + static char const* getServiceName() { return kGsiServiceName; } - static void RunStartupTasks(); + // Helper methods for GsiInstaller. + static std::string GetImagePath(const std::string& image_dir, const std::string& name); + static bool RemoveGsiFiles(const std::string& install_dir, bool wipeUserdata); + bool should_abort() const { return should_abort_; } - // This helper class will redirect writes to either a SplitFiemap or - // device-mapper. - class WriteHelper { - public: - virtual ~WriteHelper(){}; - virtual bool Write(const void* data, uint64_t bytes) = 0; - virtual bool Flush() = 0; - - WriteHelper() = default; - WriteHelper(const WriteHelper&) = delete; - WriteHelper& operator=(const WriteHelper&) = delete; - WriteHelper& operator=(WriteHelper&&) = delete; - WriteHelper(WriteHelper&&) = delete; - }; + static void RunStartupTasks(); private: using LpMetadata = android::fs_mgr::LpMetadata; @@ -89,72 +86,24 @@ class GsiService : public BinderService<GsiService>, public BnGsiService { }; int ValidateInstallParams(GsiInstallParams* params); - int StartInstall(const GsiInstallParams& params); - int PerformSanityChecks(); - int PreallocateFiles(); - int PreallocateUserdata(); - int PreallocateSystem(); - int DetermineReadWriteMethod(); - bool FormatUserdata(); - bool CommitGsiChunk(int stream_fd, int64_t bytes); - bool CommitGsiChunk(const void* data, size_t bytes); - int SetGsiBootable(bool one_shot); - int ReenableGsi(bool one_shot); bool DisableGsiInstall(); - bool AddPartitionFiemap(android::fs_mgr::MetadataBuilder* builder, - android::fs_mgr::Partition* partition, const Image& image, - const std::string& block_device); - std::unique_ptr<LpMetadata> CreateMetadata(); - std::unique_ptr<SplitFiemap> CreateFiemapWriter(const std::string& path, uint64_t size, - int* error); - bool CreateInstallStatusFile(); - bool CreateMetadataFile(); - bool SetBootMode(bool one_shot); - void PostInstallCleanup(); - - void StartAsyncOperation(const std::string& step, int64_t total_bytes); - void UpdateProgress(int status, int64_t bytes_processed); - int GetExistingImage(const LpMetadata& metadata, const std::string& name, Image* image); - std::unique_ptr<WriteHelper> OpenPartition(const std::string& name); + int ReenableGsi(bool one_shot); enum class AccessLevel { System, SystemOrShell }; binder::Status CheckUid(AccessLevel level = AccessLevel::System); - static bool RemoveGsiFiles(const std::string& install_dir, bool wipeUserdata); - static std::string GetImagePath(const std::string& image_dir, const std::string& name); static std::string GetInstalledImagePath(const std::string& name); static std::string GetInstalledImageDir(); std::mutex main_lock_; - - // Set before installation starts, to determine whether or not to delete - // the userdata image if installation fails. - bool wipe_userdata_on_failure_; + std::unique_ptr<GsiInstaller> installer_; // These are initialized or set in StartInstall(). - bool installing_ = false; std::atomic<bool> should_abort_ = false; - std::string install_dir_; - std::string userdata_gsi_path_; - std::string system_gsi_path_; - uint64_t userdata_block_size_; - uint64_t system_block_size_; - uint64_t gsi_size_; - uint64_t userdata_size_; - bool can_use_devicemapper_; - bool wipe_userdata_; - // Remaining data we're waiting to receive for the GSI image. - uint64_t gsi_bytes_written_; // Progress bar state. std::mutex progress_lock_; GsiProgress progress_; - - std::unique_ptr<WriteHelper> system_writer_; - - // This is used to track which GSI partitions have been created. - std::map<std::string, Image> partitions_; - std::unique_ptr<LpMetadata> metadata_; }; } // namespace gsi |