Allow disabling and re-enabling GSI.

This change introduces two new commands to gsi_tool: enable and disable.
Disable is similar to "wipe", in that it allows reboot back into the
normal system image. However unlike "wipe" the GSI images will not be
deleted on startup.

The GSI install can then be re-enabled with the "enable" command.
Unfortunately, this currently has a high probability of hitting a bug in
F2FS where the file cannot be repinned. This can happen if the device
has been used or rebooted extensively in between disabling and
re-enabling the GSI.

In addition, the semantics of the "install" command have changed. It
will now attempt to re-use an existing userdata if one exists. To force
a clean userdata, the --wipe parameter can be specified.

To disable a GSI install:
   gsi_tool disable
   adb reboot

To re-enable a GSI install:
   gsi_tool enable
   adb reboot

To install a GSI and preserve userdata:
   gsi_tool install --gsi-size=SIZE < image

To install a GSI and create a userdata if none exists:
   gsi_tool install --gsi-size=SIZE --userdata-size=SIZE < image

To install a GSI and wipe userdata:
   gsi_tool install --gsi-size=SIZE --userdata-size=SIZE --wipe < image

Bug: 122556707
Test: manual test
Change-Id: Ia55ca930b0b7f6b2b97a6390568555b4166e2605
diff --git a/gsi_service.cpp b/gsi_service.cpp
index 95d5255..d6b3975 100644
--- a/gsi_service.cpp
+++ b/gsi_service.cpp
@@ -17,6 +17,8 @@
 #include "gsi_service.h"
 
 #include <errno.h>
+#include <linux/fs.h>
+#include <sys/ioctl.h>
 #include <sys/stat.h>
 #include <sys/statvfs.h>
 #include <sys/types.h>
@@ -42,7 +44,7 @@
 
 using namespace std::literals;
 using namespace android::fs_mgr;
-using android::fiemap_writer::FiemapWriter;
+using namespace android::fiemap_writer;
 
 static constexpr char kUserdataDevice[] = "/dev/block/by-name/userdata";
 
@@ -52,6 +54,8 @@
 // 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() {
@@ -65,17 +69,20 @@
     PostInstallCleanup();
 }
 
-binder::Status GsiService::startGsiInstall(int64_t gsiSize, int64_t userdataSize,
+binder::Status GsiService::startGsiInstall(int64_t gsiSize, int64_t userdataSize, bool wipeUserdata,
                                            bool* _aidl_return) {
     std::lock_guard<std::mutex> guard(main_lock_);
 
     // Make sure any interrupted installations are cleaned up.
     PostInstallCleanup();
 
-    if (!StartInstall(gsiSize, userdataSize)) {
+    // Only rm userdata_gsi if one didn't already exist.
+    wipe_userdata_on_failure_ = wipeUserdata || access(kUserdataFile, F_OK);
+
+    if (!StartInstall(gsiSize, userdataSize, wipeUserdata)) {
         // Perform local cleanup and delete any lingering files.
         PostInstallCleanup();
-        RemoveGsiInstall();
+        RemoveGsiFiles(wipe_userdata_on_failure_);
         *_aidl_return = false;
     } else {
         *_aidl_return = true;
@@ -102,7 +109,11 @@
 binder::Status GsiService::setGsiBootable(bool* _aidl_return) {
     std::lock_guard<std::mutex> guard(main_lock_);
 
-    *_aidl_return = SetGsiBootable();
+    if (installing_) {
+        *_aidl_return = SetGsiBootable();
+    } else {
+        *_aidl_return = ReenableGsi();
+    }
     return binder::Status::ok();
 }
 
@@ -112,15 +123,95 @@
     // Just in case an install was left hanging.
     PostInstallCleanup();
 
-    *_aidl_return = RemoveGsiInstall();
+    if (IsGsiRunning()) {
+        // Can't remove gsi files while running.
+        *_aidl_return = UninstallGsi();
+    } else {
+        *_aidl_return = RemoveGsiFiles(true /* wipeUserdata */);
+    }
+    return binder::Status::ok();
+}
+
+binder::Status GsiService::disableGsiInstall(bool* _aidl_return) {
+    std::lock_guard<std::mutex> guard(main_lock_);
+
+    *_aidl_return = DisableGsiInstall();
     return binder::Status::ok();
 }
 
 binder::Status GsiService::isGsiRunning(bool* _aidl_return) {
+    std::lock_guard<std::mutex> guard(main_lock_);
+
     *_aidl_return = IsGsiRunning();
     return binder::Status::ok();
 }
 
+binder::Status GsiService::isGsiInstalled(bool* _aidl_return) {
+    std::lock_guard<std::mutex> guard(main_lock_);
+
+    *_aidl_return = IsGsiInstalled();
+    return binder::Status::ok();
+}
+
+binder::Status GsiService::isGsiInstallInProgress(bool* _aidl_return) {
+    std::lock_guard<std::mutex> guard(main_lock_);
+
+    *_aidl_return = installing_;
+    return binder::Status::ok();
+}
+
+binder::Status GsiService::cancelGsiInstall(bool* _aidl_return) {
+    std::lock_guard<std::mutex> guard(main_lock_);
+
+    if (!installing_) {
+        LOG(ERROR) << "No GSI installation in progress to cancel";
+        *_aidl_return = false;
+        return binder::Status::ok();
+    }
+
+    PostInstallCleanup();
+    RemoveGsiFiles(wipe_userdata_on_failure_);
+
+    *_aidl_return = true;
+    return binder::Status::ok();
+}
+
+binder::Status GsiService::getUserdataImageSize(int64_t* _aidl_return) {
+    *_aidl_return = -1;
+
+    if (installing_) {
+        // Size has already been computed.
+        *_aidl_return = userdata_size_;
+    } else if (IsGsiRunning()) {
+        // :TODO: libdm
+        android::base::unique_fd fd(open(kUserdataDevice, O_RDONLY | O_NOFOLLOW | O_CLOEXEC));
+        if (fd < 0) {
+            PLOG(ERROR) << "open " << kUserdataDevice;
+            return binder::Status::ok();
+        }
+
+        int64_t size;
+        if (ioctl(fd, BLKGETSIZE64, &size)) {
+            PLOG(ERROR) << "BLKGETSIZE64 " << kUserdataDevice;
+            return binder::Status::ok();
+        }
+        *_aidl_return = size;
+    } else {
+        // Stat the size of the userdata file.
+        struct stat s;
+        if (stat(kUserdataFile, &s)) {
+            if (errno != ENOENT) {
+                PLOG(ERROR) << "open " << kUserdataFile;
+                return binder::Status::ok();
+            }
+            *_aidl_return = 0;
+        } else {
+            *_aidl_return = s.st_size;
+        }
+    }
+    return binder::Status::ok();
+}
+
 void GsiService::PostInstallCleanup() {
     // This must be closed before unmapping partitions.
     system_fd_ = {};
@@ -131,9 +222,10 @@
     installing_ = false;
 }
 
-bool GsiService::StartInstall(int64_t gsi_size, int64_t userdata_size) {
+bool GsiService::StartInstall(int64_t gsi_size, int64_t userdata_size, bool wipe_userdata) {
     gsi_size_ = gsi_size;
     userdata_size_ = userdata_size;
+    wipe_userdata_ = wipe_userdata;
 
     if (!PerformSanityChecks() || !PreallocateFiles() || !FormatUserdata()) {
         return false;
@@ -194,36 +286,46 @@
 }
 
 bool GsiService::PreallocateFiles() {
-    // Create fallocated files.
-    auto userdata_image = CreateFiemapWriter(kUserdataFile, userdata_size_);
-    if (!userdata_image) {
-        return false;
+    if (wipe_userdata_) {
+        android::base::RemoveFileIfExists(kUserdataFile);
     }
+    android::base::RemoveFileIfExists(kSystemFile);
+
+    // TODO: trigger GC from fiemap writer.
+
+    // Create fallocated files.
+    FiemapUniquePtr userdata_image;
+    if (wipe_userdata_ || access(kUserdataFile, F_OK)) {
+        if (!userdata_size_) {
+            userdata_size_ = kDefaultUserdataSize;
+        }
+        userdata_image = CreateFiemapWriter(kUserdataFile, userdata_size_);
+        if (!userdata_image) {
+            LOG(ERROR) << "Could not create userdata image: " << kUserdataFile;
+            return false;
+        }
+        // Signal that we need to reformat userdata.
+        wipe_userdata_ = true;
+    } else {
+        userdata_image = CreateFiemapWriter(kUserdataFile, 0);
+        if (!userdata_image) {
+            LOG(ERROR) << "Could not open userdata image: " << kUserdataFile;
+            return false;
+        }
+        if (userdata_size_ && userdata_image->size() < userdata_size_) {
+            // :TODO: need to fallocate more blocks and resizefs.
+        }
+        userdata_size_ = userdata_image->size();
+    }
+
     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();
+    metadata_ = CreateMetadata(userdata_image.get(), system_image.get());
     if (!metadata_) {
-        LOG(ERROR) << "Error exporting partition table";
         return false;
     }
 
@@ -239,7 +341,8 @@
 
 fiemap_writer::FiemapUniquePtr GsiService::CreateFiemapWriter(const std::string& path,
                                                               uint64_t size) {
-    auto file = FiemapWriter::Open(path, size);
+    bool create = (size != 0);
+    auto file = FiemapWriter::Open(path, size, create);
     if (!file) {
         LOG(ERROR) << "failed to create " << path;
         return nullptr;
@@ -303,10 +406,6 @@
 }
 
 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 "
@@ -319,7 +418,7 @@
         return false;
     }
 
-    if (!CreateMetadataFile() || !CreateInstallStatusFile()) {
+    if (!CreateMetadataFile(*metadata_.get()) || !CreateInstallStatusFile()) {
         return false;
     }
 
@@ -327,13 +426,53 @@
     return true;
 }
 
-static bool RemoveGsiFiles() {
-    const std::vector<std::string> files{
-            kUserdataFile,
+bool GsiService::ReenableGsi() {
+    if (!android::gsi::IsGsiInstalled()) {
+        LOG(ERROR) << "no gsi installed - cannot re-enable";
+        return false;
+    }
+
+    std::string boot_key;
+    if (!GetInstallStatus(&boot_key)) {
+        PLOG(ERROR) << "read " << kGsiInstallStatusFile;
+        return false;
+    }
+    if (boot_key != kInstallStatusDisabled) {
+        LOG(ERROR) << "GSI is not currently disabled";
+        return false;
+    }
+
+    auto userdata_image = CreateFiemapWriter(kUserdataFile, 0);
+    if (!userdata_image) {
+        LOG(ERROR) << "could not find userdata image";
+        return false;
+    }
+    auto system_image = CreateFiemapWriter(kSystemFile, 0);
+    if (!system_image) {
+        LOG(ERROR) << "could not find system image";
+        return false;
+    }
+
+    auto metadata = CreateMetadata(userdata_image.get(), system_image.get());
+    if (!metadata) {
+        return false;
+    }
+    if (!CreateMetadataFile(*metadata.get()) || !CreateInstallStatusFile()) {
+        return false;
+    }
+    return true;
+}
+
+bool GsiService::RemoveGsiFiles(bool wipeUserdata) {
+    std::vector<std::string> files{
             kSystemFile,
             kGsiInstallStatusFile,
             kGsiLpMetadataFile,
     };
+    if (wipeUserdata) {
+        files.emplace_back(kUserdataFile);
+    }
+
     bool ok = true;
     for (const auto& file : files) {
         std::string message;
@@ -345,19 +484,24 @@
     return ok;
 }
 
-bool GsiService::RemoveGsiInstall() {
-    bool ok = false;
-    if (IsGsiRunning()) {
-        // Can't remove gsi files while running.
-        ok = UninstallGsi();
-    } else {
-        ok = RemoveGsiFiles();
+bool GsiService::DisableGsiInstall() {
+    if (!android::gsi::IsGsiInstalled()) {
+        LOG(ERROR) << "cannot disable gsi install - no install detected";
+        return false;
     }
-    installing_ = false;
-    return ok;
+    if (installing_) {
+        LOG(ERROR) << "cannot disable gsi during GSI installation";
+        return false;
+    }
+    if (!DisableGsi()) {
+        PLOG(ERROR) << "could not write gsi status";
+        return false;
+    }
+    return true;
 }
 
-std::unique_ptr<android::fs_mgr::MetadataBuilder> GsiService::CreateMetadataBuilder() {
+std::unique_ptr<LpMetadata> GsiService::CreateMetadata(FiemapWriter* userdata_image,
+                                                       FiemapWriter* system_image) {
     PartitionOpener opener;
     BlockDeviceInfo userdata_device;
     if (!opener.GetInfo("userdata", &userdata_device)) {
@@ -372,11 +516,28 @@
         return nullptr;
     }
     builder->IgnoreSlotSuffixing();
-    return builder;
+
+    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 nullptr;
+    }
+    if (!AddPartitionFiemap(builder.get(), userdata, userdata_image) ||
+        !AddPartitionFiemap(builder.get(), system, system_image)) {
+        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())) {
+bool GsiService::CreateMetadataFile(const LpMetadata& metadata) {
+    if (!WriteToImageFile(kGsiLpMetadataFile, metadata)) {
         LOG(ERROR) << "Error writing GSI partition table image";
         return false;
     }
@@ -458,7 +619,7 @@
     if (!IsGsiRunning()) {
         // Check if a wipe was requested from fastboot or adb-in-gsi.
         if (boot_key == kInstallStatusWipe) {
-            RemoveGsiFiles();
+            RemoveGsiFiles(true /* wipeUserdata */);
         }
     } else {
         int ignore;