snapshotctl: Add a test-blank-ota command for development.

The test-blank-ota command is for iterating on changes to snapuserd or
the Virtual A/B boot flow, which is normally very time consuming to
test.

It works by creating a partial OTA outside of update_engine, purely
through SnapshotManager calls. Every partition except system is
preserved. system is "updated" entirely with copy operations. The entire
"OTA" takes about ten seconds.

Tested on cuttlefish, but a physical device should work as long as
physical partitions are flashed to both slots beforehand.

The big limitation is that since update_engine does not run, there is no
merge action on reboot. This can be done manually with "snapshotctl
merge" however.

This type of tool could be easily used in the future for quickly testing
specific bugs or performance ideas in snapuserd.

Bug: N/A
Test: launch_cvd
      adb wait-for-device root
      adb shell snapshotctl test-blank-ota
      adb reboot
      adb wait-for-device root
      adb shell snapshotctl dump
Change-Id: I5911440cbe14ee909905a0b0fc17da95ba4c5d68
diff --git a/fs_mgr/libsnapshot/Android.bp b/fs_mgr/libsnapshot/Android.bp
index 474d482..8e4b556 100644
--- a/fs_mgr/libsnapshot/Android.bp
+++ b/fs_mgr/libsnapshot/Android.bp
@@ -324,6 +324,22 @@
         "libstatslog",
         "libutils",
     ],
+    header_libs: [
+        "libstorage_literals_headers",
+    ],
+    product_variables: {
+        debuggable: {
+            cppflags: [
+                "-DSNAPSHOTCTL_USERDEBUG_OR_ENG",
+            ],
+            shared_libs: [
+                "android.hardware.boot@1.0",
+                "android.hardware.boot@1.1",
+                "android.hardware.boot-V1-ndk",
+                "libboot_control_client",
+            ],
+        },
+    },
 }
 
 cc_test {
diff --git a/fs_mgr/libsnapshot/snapshotctl.cpp b/fs_mgr/libsnapshot/snapshotctl.cpp
index 67189d4..ad3f83c 100644
--- a/fs_mgr/libsnapshot/snapshotctl.cpp
+++ b/fs_mgr/libsnapshot/snapshotctl.cpp
@@ -25,9 +25,27 @@
 #include <android-base/logging.h>
 #include <android-base/unique_fd.h>
 
+#include <fs_mgr.h>
+#include <fs_mgr_dm_linear.h>
+#include <fstab/fstab.h>
+#include <liblp/builder.h>
+#include <libsnapshot/cow_format.h>
 #include <libsnapshot/snapshot.h>
+#include <storage_literals/storage_literals.h>
 
+#ifdef SNAPSHOTCTL_USERDEBUG_OR_ENG
+#include <BootControlClient.h>
+#endif
+
+using namespace std::chrono_literals;
 using namespace std::string_literals;
+using namespace android::storage_literals;
+using android::fs_mgr::CreateLogicalPartitionParams;
+using android::fs_mgr::FindPartition;
+using android::fs_mgr::GetPartitionSize;
+using android::fs_mgr::PartitionOpener;
+using android::fs_mgr::ReadMetadata;
+using android::fs_mgr::SlotNumberForSlotSuffix;
 
 int Usage() {
     std::cerr << "snapshotctl: Control snapshots.\n"
@@ -67,11 +85,136 @@
     return false;
 }
 
+#ifdef SNAPSHOTCTL_USERDEBUG_OR_ENG
+bool CreateTestUpdate(SnapshotManager* sm) {
+    chromeos_update_engine::DeltaArchiveManifest manifest;
+
+    // We only copy system, to simplify things.
+    manifest.set_partial_update(true);
+
+    auto dap = manifest.mutable_dynamic_partition_metadata();
+    dap->set_snapshot_enabled(true);
+    dap->set_vabc_enabled(true);
+    dap->set_vabc_compression_param("none");
+    dap->set_cow_version(kCowVersionMajor);
+
+    auto source_slot = fs_mgr_get_slot_suffix();
+    auto source_slot_number = SlotNumberForSlotSuffix(source_slot);
+    auto target_slot = fs_mgr_get_other_slot_suffix();
+    auto target_slot_number = SlotNumberForSlotSuffix(target_slot);
+    auto super_source = fs_mgr_get_super_partition_name(source_slot_number);
+
+    // Get current partition information.
+    PartitionOpener opener;
+    auto source_metadata = ReadMetadata(opener, super_source, source_slot_number);
+    if (!source_metadata) {
+        std::cerr << "Could not read source partition metadata.\n";
+        return false;
+    }
+
+    auto system_source_name = "system" + source_slot;
+    auto system_source = FindPartition(*source_metadata.get(), system_source_name);
+    if (!system_source) {
+        std::cerr << "Could not find system partition: " << system_source_name << ".\n";
+        return false;
+    }
+    auto system_source_size = GetPartitionSize(*source_metadata.get(), *system_source);
+
+    // Since we only add copy operations, 64MB should be enough.
+    auto system_update = manifest.mutable_partitions()->Add();
+    system_update->set_partition_name("system");
+    system_update->set_estimate_cow_size(64_MiB);
+    system_update->mutable_new_partition_info()->set_size(system_source_size);
+
+    if (!sm->CreateUpdateSnapshots(manifest)) {
+        std::cerr << "Could not create update snapshots.\n";
+        return false;
+    }
+
+    // Write the "new" system partition.
+    auto system_target_name = "system" + target_slot;
+    auto source_device = "/dev/block/mapper/" + system_source_name;
+    CreateLogicalPartitionParams clpp = {
+            .block_device = fs_mgr_get_super_partition_name(target_slot_number),
+            .metadata_slot = {target_slot_number},
+            .partition_name = system_target_name,
+            .partition_opener = &opener,
+            .timeout_ms = 10s,
+    };
+    auto writer = sm->OpenSnapshotWriter(clpp, {source_device});
+    if (!writer) {
+        std::cerr << "Could not open snapshot writer.\n";
+        return false;
+    }
+    if (!writer->Initialize()) {
+        std::cerr << "Could not initialize snapshot for writing.\n";
+        return false;
+    }
+
+    for (uint64_t block = 0; block < system_source_size / 4096; block++) {
+        if (!writer->AddCopy(block, block)) {
+            std::cerr << "Unable to add copy operation for block " << block << ".\n";
+            return false;
+        }
+    }
+    if (!writer->Finalize()) {
+        std::cerr << "Could not finalize COW for " << system_target_name << ".\n";
+        return false;
+    }
+    writer = nullptr;
+
+    // Finished writing this partition, unmap.
+    if (!sm->UnmapUpdateSnapshot(system_target_name)) {
+        std::cerr << "Could not unmap snapshot for " << system_target_name << ".\n";
+        return false;
+    }
+
+    // All snapshots have been written.
+    if (!sm->FinishedSnapshotWrites(false /* wipe */)) {
+        std::cerr << "Could not finalize snapshot writes.\n";
+        return false;
+    }
+
+    auto hal = hal::BootControlClient::WaitForService();
+    if (!hal) {
+        std::cerr << "Could not find IBootControl HAL.\n";
+        return false;
+    }
+    auto cr = hal->SetActiveBootSlot(target_slot_number);
+    if (!cr.IsOk()) {
+        std::cerr << "Could not set active boot slot: " << cr.errMsg;
+        return false;
+    }
+
+    std::cerr << "It is now safe to reboot your device. If using a physical device, make\n"
+              << "sure that all physical partitions are flashed to both A and B slots.\n";
+    return true;
+}
+
+bool TestOtaHandler(int /* argc */, char** /* argv */) {
+    auto sm = SnapshotManager::New();
+
+    if (!sm->BeginUpdate()) {
+        std::cerr << "Error starting update.\n";
+        return false;
+    }
+
+    if (!CreateTestUpdate(sm.get())) {
+        sm->CancelUpdate();
+        return false;
+    }
+    return true;
+}
+#endif
+
 static std::map<std::string, std::function<bool(int, char**)>> kCmdMap = {
         // clang-format off
         {"dump", DumpCmdHandler},
         {"merge", MergeCmdHandler},
         {"map", MapCmdHandler},
+#ifdef SNAPSHOTCTL_USERDEBUG_OR_ENG
+        {"test-blank-ota", TestOtaHandler},
+#endif
         {"unmap", UnmapCmdHandler},
         // clang-format on
 };