Introduce a daemon (gsid) for managing GSI installs.
Bug: 122556707
Test: manual test
Change-Id: I96d833f77740e6b791992336e590dd1a089895c9
diff --git a/gsi_service.cpp b/gsi_service.cpp
new file mode 100644
index 0000000..7e7f827
--- /dev/null
+++ b/gsi_service.cpp
@@ -0,0 +1,437 @@
+/*
+ * 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_service.h"
+
+#include <errno.h>
+#include <sys/stat.h>
+#include <sys/statvfs.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <chrono>
+#include <string>
+#include <vector>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <android/gsi/IGsiService.h>
+#include <fs_mgr_dm_linear.h>
+#include <libfiemap_writer/fiemap_writer.h>
+#include <logwrap/logwrap.h>
+#include "file_paths.h"
+
+namespace android {
+namespace gsi {
+
+using namespace std::literals;
+using namespace android::fs_mgr;
+using android::fiemap_writer::FiemapWriter;
+
+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;
+static constexpr std::chrono::milliseconds kDmTimeout = 5000ms;
+
+void GsiService::Register() {
+ auto ret = android::BinderService<GsiService>::publish();
+ if (ret != android::OK) {
+ LOG(FATAL) << "Could not register gsi service: " << ret;
+ }
+}
+
+GsiService::~GsiService() {
+ PostInstallCleanup();
+}
+
+binder::Status GsiService::startGsiInstall(int64_t gsiSize, int64_t userdataSize,
+ bool* _aidl_return) {
+ std::lock_guard<std::mutex> guard(main_lock_);
+
+ // Make sure any interrupted installations are cleaned up.
+ PostInstallCleanup();
+
+ if (!StartInstall(gsiSize, userdataSize)) {
+ // Perform local cleanup and delete any lingering files.
+ PostInstallCleanup();
+ RemoveGsiInstall();
+ *_aidl_return = false;
+ } else {
+ *_aidl_return = true;
+ }
+ return binder::Status::ok();
+}
+
+binder::Status GsiService::commitGsiChunkFromStream(const android::base::unique_fd& stream,
+ int64_t bytes, bool* _aidl_return) {
+ std::lock_guard<std::mutex> guard(main_lock_);
+
+ *_aidl_return = CommitGsiChunk(stream, bytes);
+ return binder::Status::ok();
+}
+
+binder::Status GsiService::commitGsiChunkFromMemory(const std::vector<uint8_t>& bytes,
+ bool* _aidl_return) {
+ std::lock_guard<std::mutex> guard(main_lock_);
+
+ *_aidl_return = CommitGsiChunk(bytes.data(), bytes.size());
+ return binder::Status::ok();
+}
+
+binder::Status GsiService::setGsiBootable(bool* _aidl_return) {
+ std::lock_guard<std::mutex> guard(main_lock_);
+
+ *_aidl_return = SetGsiBootable();
+ return binder::Status::ok();
+}
+
+binder::Status GsiService::removeGsiInstall(bool* _aidl_return) {
+ std::lock_guard<std::mutex> guard(main_lock_);
+
+ // Just in case an install was left hanging.
+ PostInstallCleanup();
+
+ *_aidl_return = RemoveGsiInstall();
+ return binder::Status::ok();
+}
+
+binder::Status GsiService::isGsiRunning(bool* _aidl_return) {
+ *_aidl_return = IsGsiRunning();
+ return binder::Status::ok();
+}
+
+void GsiService::PostInstallCleanup() {
+ // This must be closed before unmapping partitions.
+ system_fd_ = {};
+
+ DestroyLogicalPartition("userdata_gsi", kDmTimeout);
+ DestroyLogicalPartition("system_gsi", kDmTimeout);
+
+ installing_ = false;
+}
+
+bool GsiService::StartInstall(int64_t gsi_size, int64_t userdata_size) {
+ gsi_size_ = gsi_size;
+ userdata_size_ = userdata_size;
+
+ if (!PerformSanityChecks() || !PreallocateFiles() || !FormatUserdata()) {
+ return false;
+ }
+
+ // Map system_gsi so we can write to it.
+ std::string block_device;
+ if (!CreateLogicalPartition(kUserdataDevice, *metadata_.get(), "system_gsi", true, kDmTimeout,
+ &block_device)) {
+ LOG(ERROR) << "Error creating device-mapper node for system_gsi";
+ return false;
+ }
+
+ static const int kOpenFlags = O_RDWR | O_NOFOLLOW | O_CLOEXEC;
+ system_fd_.reset(open(block_device.c_str(), kOpenFlags));
+ if (system_fd_ < 0) {
+ LOG(ERROR) << "could not open " << block_device << ": " << strerror(errno);
+ return false;
+ }
+
+ installing_ = true;
+ return true;
+}
+
+bool GsiService::PerformSanityChecks() {
+ if (gsi_size_ < 0) {
+ LOG(ERROR) << "image size " << gsi_size_ << " is negative";
+ return false;
+ }
+
+ if (!EnsureFolderExists(kGsiDataFolder) || !EnsureFolderExists(kGsiMetadataFolder)) {
+ return false;
+ }
+
+ struct statvfs sb;
+ if (statvfs(kGsiDataFolder, &sb)) {
+ LOG(ERROR) << "failed to read file system stats: " << strerror(errno);
+ return false;
+ }
+
+ // 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 false;
+ }
+ // 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 false;
+ }
+ return true;
+}
+
+bool GsiService::PreallocateFiles() {
+ // Create fallocated files.
+ auto userdata_image = CreateFiemapWriter(kUserdataFile, userdata_size_);
+ if (!userdata_image) {
+ return false;
+ }
+ auto system_image = CreateFiemapWriter(kSystemFile, gsi_size_);
+ if (!system_image) {
+ return false;
+ }
+
+ // Save the extent information in liblp.
+ auto builder = CreateMetadataBuilder();
+ if (!builder) {
+ return false;
+ }
+
+ Partition* userdata = builder->AddPartition("userdata_gsi", LP_PARTITION_ATTR_NONE);
+ Partition* system = builder->AddPartition("system_gsi", LP_PARTITION_ATTR_READONLY);
+ if (!userdata || !system) {
+ LOG(ERROR) << "Error creating partition table";
+ return false;
+ }
+ if (!AddPartitionFiemap(builder.get(), userdata, userdata_image.get()) ||
+ !AddPartitionFiemap(builder.get(), system, system_image.get())) {
+ return false;
+ }
+
+ metadata_ = builder->Export();
+ if (!metadata_) {
+ LOG(ERROR) << "Error exporting partition table";
+ return false;
+ }
+
+ system_image->Flush();
+ userdata_image->Flush();
+
+ // We're ready to start streaming data in.
+ gsi_bytes_written_ = 0;
+ userdata_block_size_ = userdata_image->block_size();
+ system_block_size_ = system_image->block_size();
+ return true;
+}
+
+fiemap_writer::FiemapUniquePtr GsiService::CreateFiemapWriter(const std::string& path,
+ uint64_t size) {
+ auto file = FiemapWriter::Open(path, size);
+ if (!file) {
+ LOG(ERROR) << "failed to create " << path;
+ return nullptr;
+ }
+
+ uint64_t extents = file->extents().size();
+ if (extents > kMaximumExtents) {
+ LOG(ERROR) << "file " << path << " has too many extents: " << extents;
+ return nullptr;
+ }
+ return file;
+}
+
+bool GsiService::CommitGsiChunk(const android::base::unique_fd& stream, int64_t bytes) {
+ if (bytes < 0) {
+ LOG(ERROR) << "chunk size " << bytes << " is negative";
+ return false;
+ }
+
+ auto buffer = std::make_unique<char[]>(system_block_size_);
+
+ 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, buffer.get(), max_to_read));
+ if (rv < 0) {
+ LOG(ERROR) << "read: " << strerror(errno);
+ 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;
+ }
+ 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 (!android::base::WriteFully(system_fd_, data, bytes)) {
+ LOG(ERROR) << "write failed: " << strerror(errno);
+ return false;
+ }
+ gsi_bytes_written_ += bytes;
+ return true;
+}
+
+bool GsiService::SetGsiBootable() {
+ if (!installing_) {
+ LOG(ERROR) << "no gsi installation in progress";
+ return false;
+ }
+ 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 false;
+ }
+
+ if (fsync(system_fd_)) {
+ LOG(ERROR) << "fsync failed: " << strerror(errno);
+ return false;
+ }
+
+ if (!CreateMetadataFile() || !CreateBootableFile()) {
+ return false;
+ }
+
+ PostInstallCleanup();
+ return true;
+}
+
+bool GsiService::RemoveGsiInstall() {
+ const std::vector<std::string> files{
+ kUserdataFile,
+ kSystemFile,
+ kGsiBootableFile,
+ kGsiLpMetadataFile,
+ };
+ bool ok = true;
+ for (const auto& file : files) {
+ std::string message;
+ if (!android::base::RemoveFileIfExists(file, &message)) {
+ LOG(ERROR) << message;
+ ok = false;
+ }
+ }
+ installing_ = false;
+ return ok;
+}
+
+std::unique_ptr<android::fs_mgr::MetadataBuilder> GsiService::CreateMetadataBuilder() {
+ PartitionOpener opener;
+ BlockDeviceInfo userdata_device;
+ if (!opener.GetInfo("userdata", &userdata_device)) {
+ LOG(ERROR) << "Error reading userdata partition";
+ return nullptr;
+ }
+
+ std::vector<BlockDeviceInfo> block_devices = {userdata_device};
+ auto builder = MetadataBuilder::New(block_devices, "userdata", 128 * 1024, 1);
+ if (!builder) {
+ LOG(ERROR) << "Error creating metadata builder";
+ return nullptr;
+ }
+ builder->IgnoreSlotSuffixing();
+ return builder;
+}
+
+bool GsiService::CreateMetadataFile() {
+ if (!WriteToImageFile(kGsiLpMetadataFile, *metadata_.get())) {
+ LOG(ERROR) << "Error writing GSI partition table image";
+ return false;
+ }
+ return true;
+}
+
+bool GsiService::FormatUserdata() {
+ std::string block_device;
+ if (!CreateLogicalPartition(kUserdataDevice, *metadata_.get(), "userdata_gsi", true, kDmTimeout,
+ &block_device)) {
+ LOG(ERROR) << "Error creating device-mapper node for userdata_gsi";
+ return false;
+ }
+
+ std::string block_size = std::to_string(userdata_block_size_);
+ const char* const mke2fs_args[] = {
+ "/system/bin/mke2fs", "-t", "ext4", "-b", block_size.c_str(),
+ block_device.c_str(), nullptr,
+ };
+ int rc = android_fork_execvp(arraysize(mke2fs_args), const_cast<char**>(mke2fs_args), nullptr,
+ true, true);
+ if (rc) {
+ LOG(ERROR) << "mke2fs returned " << rc;
+ return false;
+ }
+ return true;
+}
+
+bool GsiService::AddPartitionFiemap(MetadataBuilder* builder, Partition* partition,
+ FiemapWriter* writer) {
+ for (const auto& extent : 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 = extent.fe_length / LP_SECTOR_SIZE;
+ uint64_t physical_sector = extent.fe_physical / LP_SECTOR_SIZE;
+ if (!builder->AddLinearExtent(partition, "userdata", num_sectors, physical_sector)) {
+ LOG(ERROR) << "Could not add extent to lp metadata";
+ return false;
+ }
+ }
+ return true;
+}
+
+bool GsiService::CreateBootableFile() {
+ android::base::unique_fd fd(
+ open(kGsiBootableFile, O_WRONLY | O_CLOEXEC | O_NOFOLLOW | O_CREAT, 0755));
+ if (fd < 0) {
+ LOG(ERROR) << "open: " << strerror(errno) << ": " << kGsiBootableFile;
+ return false;
+ }
+ return true;
+}
+
+bool GsiService::EnsureFolderExists(const std::string& path) {
+ if (!mkdir(path.c_str(), 0755) || errno == EEXIST) {
+ return true;
+ }
+
+ LOG(ERROR) << "mkdir: " << strerror(errno) << ": " << path;
+ return false;
+}
+
+} // namespace gsi
+} // namespace android