summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Android.bp1
-rw-r--r--file_paths.h3
-rw-r--r--gsi_installer.cpp665
-rw-r--r--gsi_installer.h124
-rw-r--r--gsi_service.cpp681
-rw-r--r--gsi_service.h79
6 files changed, 836 insertions, 717 deletions
diff --git a/Android.bp b/Android.bp
index 4cefc42..8d52ae0 100644
--- a/Android.bp
+++ b/Android.bp
@@ -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