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