From e1ba5bc721daf65598feb2b5c46d1c06fac63ec4 Mon Sep 17 00:00:00 2001
From: Tom Marshall <tdm@cyngn.com>
Date: Fri, 6 Nov 2015 10:19:13 -0800
Subject: recovery: datamedia support

Change-Id: I4cef82973a15111bee92cd7c81f0e1db8d211991
---
 device.cpp   |  8 +++---
 device.h     | 12 ++++++---
 recovery.cpp | 37 +++++++++++++++++++++++---
 roots.cpp    | 87 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 roots.h      |  2 --
 5 files changed, 132 insertions(+), 14 deletions(-)

diff --git a/device.cpp b/device.cpp
index 46ee880..1f6bac7 100644
--- a/device.cpp
+++ b/device.cpp
@@ -22,6 +22,7 @@ static const char* MENU_ITEMS[] = {
     "Apply update",
     "Wipe data/factory reset",
     "Wipe cache partition",
+    "Wipe media",
     "Mount /system",
     "View recovery logs",
     "Power off",
@@ -41,9 +42,10 @@ Device::BuiltinAction Device::InvokeMenuItem(int menu_position) {
     case 2: return APPLY_UPDATE;
     case 3: return WIPE_DATA;
     case 4: return WIPE_CACHE;
-    case 5: return MOUNT_SYSTEM;
-    case 6: return VIEW_RECOVERY_LOGS;
-    case 7: return SHUTDOWN;
+    case 5: return WIPE_MEDIA;
+    case 6: return MOUNT_SYSTEM;
+    case 7: return VIEW_RECOVERY_LOGS;
+    case 8: return SHUTDOWN;
     default: return NO_ACTION;
   }
 }
diff --git a/device.h b/device.h
index d761d2a..c11f52e 100644
--- a/device.h
+++ b/device.h
@@ -64,10 +64,11 @@ class Device : public VoldWatcher {
         // APPLY_ADB_SIDELOAD was 4.
         WIPE_DATA = 5,
         WIPE_CACHE = 6,
-        REBOOT_BOOTLOADER = 7,
-        SHUTDOWN = 8,
-        VIEW_RECOVERY_LOGS = 9,
-        MOUNT_SYSTEM = 10,
+        WIPE_MEDIA = 7,
+        REBOOT_BOOTLOADER = 8,
+        SHUTDOWN = 9,
+        VIEW_RECOVERY_LOGS = 10,
+        MOUNT_SYSTEM = 11,
     };
 
     // Return the list of menu items (an array of strings,
@@ -104,6 +105,9 @@ class Device : public VoldWatcher {
     virtual bool PreWipeData() { return true; }
     virtual bool PostWipeData() { return true; }
 
+    virtual bool PreWipeMedia() { return true; }
+    virtual bool PostWipeMedia() { return true; }
+
     // Called before reboot
     virtual char const* GetRebootReason() { return ""; }
 
diff --git a/recovery.cpp b/recovery.cpp
index 5c482e0..d3dc398 100644
--- a/recovery.cpp
+++ b/recovery.cpp
@@ -66,6 +66,7 @@ static const struct option OPTIONS[] = {
   { "update_package", required_argument, NULL, 'u' },
   { "wipe_data", no_argument, NULL, 'w' },
   { "wipe_cache", no_argument, NULL, 'c' },
+  { "wipe_media", no_argument, NULL, 'm' },
   { "show_text", no_argument, NULL, 't' },
   { "sideload", no_argument, NULL, 's' },
   { "sideload_auto_reboot", no_argument, NULL, 'a' },
@@ -564,7 +565,9 @@ static bool erase_volume(const char* volume) {
 
     ui->Print("Formatting %s...\n", volume);
 
-    ensure_path_unmounted(volume);
+    if (volume[0] == '/') {
+        ensure_path_unmounted(volume);
+    }
     int result = format_volume(volume);
 
     if (is_cache) {
@@ -765,7 +768,7 @@ static bool yes_no(Device* device, const char* question1, const char* question2)
 }
 
 // Return true on success.
-static bool wipe_data(int should_confirm, Device* device) {
+static bool wipe_data(int should_confirm, Device* device, bool force = false) {
     if (should_confirm && !yes_no(device, "Wipe all user data?", "  THIS CAN NOT BE UNDONE!")) {
         return false;
     }
@@ -775,13 +778,29 @@ static bool wipe_data(int should_confirm, Device* device) {
     ui->Print("\n-- Wiping data...\n");
     bool success =
         device->PreWipeData() &&
-        erase_volume("/data") &&
+        erase_volume("/data", force) &&
         erase_volume("/cache") &&
         device->PostWipeData();
     ui->Print("Data wipe %s.\n", success ? "complete" : "failed");
     return success;
 }
 
+static bool wipe_media(int should_confirm, Device* device) {
+    if (should_confirm && !yes_no(device, "Wipe all user media?", "  THIS CAN NOT BE UNDONE!")) {
+        return false;
+    }
+
+    modified_flash = true;
+
+    ui->Print("\n-- Wiping media...\n");
+    bool success =
+        device->PreWipeMedia() &&
+        erase_volume("media") &&
+        device->PostWipeMedia();
+    ui->Print("Media wipe %s.\n", success ? "complete" : "failed");
+    return success;
+}
+
 // Return true on success.
 static bool wipe_cache(bool should_confirm, Device* device) {
     if (should_confirm && !yes_no(device, "Wipe cache?", "  THIS CAN NOT BE UNDONE!")) {
@@ -992,6 +1011,11 @@ prompt_and_wait(Device* device, int status) {
                     if (!ui->IsTextVisible()) return Device::NO_ACTION;
                     break;
 
+                case Device::WIPE_MEDIA:
+                    wipe_media(ui->IsTextVisible(), device);
+                    if (!ui->IsTextVisible()) return Device::NO_ACTION;
+                    break;
+
                 case Device::APPLY_UPDATE:
                     {
                         status = show_apply_update_menu(device);
@@ -1211,6 +1235,7 @@ main(int argc, char **argv) {
     const char *update_package = NULL;
     bool should_wipe_data = false;
     bool should_wipe_cache = false;
+    bool should_wipe_media = false;
     bool show_text = false;
     bool sideload = false;
     bool sideload_auto_reboot = false;
@@ -1334,13 +1359,17 @@ main(int argc, char **argv) {
             }
         }
     } else if (should_wipe_data) {
-        if (!wipe_data(false, device)) {
+        if (!wipe_data(false, device, should_wipe_media)) {
             status = INSTALL_ERROR;
         }
     } else if (should_wipe_cache) {
         if (!wipe_cache(false, device)) {
             status = INSTALL_ERROR;
         }
+    } else if (should_wipe_media) {
+        if (!wipe_media(false, device)) {
+            status = INSTALL_ERROR;
+        }
     } else if (sideload) {
         // 'adb reboot sideload' acts the same as user presses key combinations
         // to enter the sideload mode. When 'sideload-auto-reboot' is used, text
diff --git a/roots.cpp b/roots.cpp
index b4860d7..fda8f61 100644
--- a/roots.cpp
+++ b/roots.cpp
@@ -309,7 +309,60 @@ static int exec_cmd(const char* path, char* const argv[]) {
     return WEXITSTATUS(status);
 }
 
+static int rmtree_except(const char* path, const char* except)
+{
+    char pathbuf[PATH_MAX];
+    int rc = 0;
+    DIR* dp = opendir(path);
+    if (dp == NULL) {
+        return -1;
+    }
+    struct dirent* de;
+    while ((de = readdir(dp)) != NULL) {
+        if (!strcmp(de->d_name, ".") || !strcmp(de->d_name, ".."))
+            continue;
+        if (except && !strcmp(de->d_name, except))
+            continue;
+        struct stat st;
+        snprintf(pathbuf, sizeof(pathbuf), "%s/%s", path, de->d_name);
+        rc = lstat(pathbuf, &st);
+        if (rc != 0) {
+            LOGE("Failed to stat %s\n", pathbuf);
+            break;
+        }
+        if (S_ISDIR(st.st_mode)) {
+            rc = rmtree_except(pathbuf, NULL);
+            if (rc != 0)
+                break;
+            rc = rmdir(pathbuf);
+        }
+        else {
+            rc = unlink(pathbuf);
+        }
+        if (rc != 0) {
+            LOGI("Failed to remove %s: %s\n", pathbuf, strerror(errno));
+            break;
+        }
+    }
+    closedir(dp);
+    return rc;
+}
+
 int format_volume(const char* volume) {
+    if (strcmp(volume, "media") == 0) {
+        if (!vdc->isEmulatedStorage()) {
+            return 0;
+        }
+        if (ensure_path_mounted("/data") != 0) {
+            LOGE("format_volume failed to mount /data\n");
+            return -1;
+        }
+        int rc = 0;
+        rc = rmtree_except("/data/media", NULL);
+        ensure_path_unmounted("/data");
+        return rc;
+    }
+
     Volume* v = volume_for_path(volume);
     if (v == NULL) {
         LOGE("unknown volume \"%s\"\n", volume);
@@ -325,6 +378,38 @@ int format_volume(const char* volume) {
         return -1;
     }
 
+    if (strcmp(volume, "/data") == 0 && vdc->isEmulatedStorage()) {
+        if (ensure_path_mounted("/data") == 0) {
+            // Preserve .layout_version to avoid "nesting bug"
+            LOGI("Preserving layout version\n");
+            unsigned char layout_buf[256];
+            ssize_t layout_buflen = -1;
+            int fd;
+            fd = open("/data/.layout_version", O_RDONLY);
+            if (fd != -1) {
+                layout_buflen = read(fd, layout_buf, sizeof(layout_buf));
+                close(fd);
+            }
+
+            int rc = rmtree_except("/data", "media");
+
+            // Restore .layout_version
+            if (layout_buflen > 0) {
+                LOGI("Restoring layout version\n");
+                fd = open("/data/.layout_version", O_WRONLY | O_CREAT | O_EXCL, 0600);
+                if (fd != -1) {
+                    write(fd, layout_buf, layout_buflen);
+                    close(fd);
+                }
+            }
+
+            ensure_path_unmounted(volume);
+
+            return rc;
+        }
+        LOGE("format_volume failed to mount /data, formatting instead\n");
+    }
+
     if (ensure_path_unmounted(volume) != 0) {
         LOGE("format_volume failed to unmount \"%s\"\n", v->mount_point);
         return -1;
@@ -431,7 +516,7 @@ int setup_install_mounts() {
             // datamedia and anything managed by vold must be unmounted
             // with the detach flag to ensure that FUSE works.
             bool detach = false;
-            if (is_data_media() && strcmp(v->mount_point, "/data") == 0) {
+            if (vdc->isEmulatedStorage() && strcmp(v->mount_point, "/data") == 0) {
                 detach = true;
             }
             if (ensure_volume_unmounted(v, detach) != 0) {
diff --git a/roots.h b/roots.h
index e38707e..9b369c0 100644
--- a/roots.h
+++ b/roots.h
@@ -50,8 +50,6 @@ int setup_install_mounts();
 
 int get_num_volumes();
 
-int is_data_media();
-
 bool volume_is_mountable(Volume *v);
 bool volume_is_readonly(Volume *v);
 bool volume_is_verity(Volume *v);
-- 
cgit v1.1