init: Issue a wipe on boot if trade-in mode was active.

This modifies first-stage init to check for /metadata/tradeinmode/wipe
as soon as /metadata is mounted. If the file exists, we issue a request
to the bootloader to reboot to recovery and wipe /data. Since this also
wipes /metadata, the wipe indicator will be removed too.

In case some kind of failure happens in recovery, this also implements a
quick-and-dirty counter mechanism to fallback to the recovery menu.

Bug: 307713521
Test: touch /metadata/tradeinmode/wipe && adb reboot
Change-Id: I2d05903cadcdadf9c05f6736454db790a9e6b5bb
diff --git a/init/Android.bp b/init/Android.bp
index 18a79d6..5e06000 100644
--- a/init/Android.bp
+++ b/init/Android.bp
@@ -390,6 +390,7 @@
         "libsnapshot_init",
         "update_metadata-protos",
         "libprocinfo",
+        "libbootloader_message",
     ],
 
     static_executable: true,
diff --git a/init/first_stage_mount.cpp b/init/first_stage_mount.cpp
index ece430b..f303815 100644
--- a/init/first_stage_mount.cpp
+++ b/init/first_stage_mount.cpp
@@ -32,9 +32,12 @@
 #include <android-base/chrono_utils.h>
 #include <android-base/file.h>
 #include <android-base/logging.h>
+#include <android-base/parseint.h>
 #include <android-base/stringprintf.h>
 #include <android-base/strings.h>
 #include <android/avf_cc_flags.h>
+#include <bootloader_message/bootloader_message.h>
+#include <cutils/android_reboot.h>
 #include <fs_avb/fs_avb.h>
 #include <fs_mgr.h>
 #include <fs_mgr_dm_linear.h>
@@ -46,6 +49,7 @@
 
 #include "block_dev_initializer.h"
 #include "devices.h"
+#include "reboot_utils.h"
 #include "result.h"
 #include "snapuserd_transition.h"
 #include "switch_root.h"
@@ -111,6 +115,8 @@
     bool GetDmVerityDevices(std::set<std::string>* devices);
     bool SetUpDmVerity(FstabEntry* fstab_entry);
 
+    void RequestTradeInModeWipeIfNeeded();
+
     bool InitAvbHandle();
 
     bool need_dm_verity_;
@@ -263,6 +269,8 @@
 }
 
 bool FirstStageMountVBootV2::DoFirstStageMount() {
+    RequestTradeInModeWipeIfNeeded();
+
     if (!IsDmLinearEnabled() && fstab_.empty()) {
         // Nothing to mount.
         LOG(INFO) << "First stage mount skipped (missing/incompatible/empty fstab in device tree)";
@@ -883,6 +891,55 @@
     return true;
 }
 
+void FirstStageMountVBootV2::RequestTradeInModeWipeIfNeeded() {
+    static constexpr const char* kWipeIndicator = "/metadata/tradeinmode/wipe";
+    static constexpr size_t kWipeAttempts = 3;
+
+    if (access(kWipeIndicator, R_OK) == -1) {
+        return;
+    }
+
+    // Write a counter to the wipe indicator, to try and prevent boot loops if
+    // recovery fails to wipe data.
+    uint32_t counter = 0;
+    std::string contents;
+    if (ReadFileToString(kWipeIndicator, &contents)) {
+        android::base::ParseUint(contents, &counter);
+        contents = std::to_string(++counter);
+        if (android::base::WriteStringToFile(contents, kWipeIndicator)) {
+            sync();
+        } else {
+            PLOG(ERROR) << "Failed to update " << kWipeIndicator;
+        }
+    } else {
+        PLOG(ERROR) << "Failed to read " << kWipeIndicator;
+    }
+
+    std::string err;
+    auto misc_device = get_misc_blk_device(&err);
+    if (misc_device.empty()) {
+        LOG(FATAL) << "Could not find misc device: " << err;
+    }
+
+    auto misc_name = android::base::Basename(misc_device);
+    if (!block_dev_init_.InitDevices({misc_name})) {
+        LOG(FATAL) << "Could not find misc device: " << misc_device;
+    }
+
+    // If we've failed to wipe three times, don't include the wipe command. This
+    // will force us to boot into the recovery menu instead where a manual wipe
+    // can be attempted.
+    std::vector<std::string> options;
+    if (counter <= kWipeAttempts) {
+        options.emplace_back("--wipe_data");
+        options.emplace_back("--reason=tradeinmode");
+    }
+    if (!write_bootloader_message(options, &err)) {
+        LOG(FATAL) << "Could not issue wipe: " << err;
+    }
+    RebootSystem(ANDROID_RB_RESTART2, "recovery", "reboot,tradeinmode,wipe");
+}
+
 void SetInitAvbVersionInRecovery() {
     if (!IsRecoveryMode()) {
         LOG(INFO) << "Skipped setting INIT_AVB_VERSION (not in recovery mode)";
diff --git a/rootdir/init.rc b/rootdir/init.rc
index 1acd637..f43c718 100644
--- a/rootdir/init.rc
+++ b/rootdir/init.rc
@@ -644,6 +644,7 @@
     mkdir /metadata/ota 0750 root system
     mkdir /metadata/ota/snapshots 0750 root system
     mkdir /metadata/watchdog 0770 root system
+    mkdir /metadata/tradeinmode 0770 root system
 
     mkdir /metadata/apex 0700 root system
     mkdir /metadata/apex/sessions 0700 root system