Merge "Add a stub recovery UI."
diff --git a/bootloader_message/include/bootloader_message/bootloader_message.h b/bootloader_message/include/bootloader_message/bootloader_message.h
index b3d2182..ec47fac 100644
--- a/bootloader_message/include/bootloader_message/bootloader_message.h
+++ b/bootloader_message/include/bootloader_message/bootloader_message.h
@@ -42,8 +42,9 @@
  * It is also updated by the bootloader when firmware update
  * is complete (to boot into recovery for any final cleanup)
  *
- * The status field is written by the bootloader after the
- * completion of an "update-radio" or "update-hboot" command.
+ * The status field was used by the bootloader after the completion
+ * of an "update-radio" or "update-hboot" command, which has been
+ * deprecated since Froyo.
  *
  * The recovery field is only written by linux and used
  * for the system to send a message to recovery or the
diff --git a/install.cpp b/install.cpp
index dd6ed81..b23586c 100644
--- a/install.cpp
+++ b/install.cpp
@@ -335,15 +335,6 @@
     //            progress bar within the segment defined by the most
     //            recent progress command.
     //
-    //        firmware <"hboot"|"radio"> <filename>
-    //            arrange to install the contents of <filename> in the
-    //            given partition on reboot.
-    //
-    //            (API v2: <filename> may start with "PACKAGE:" to
-    //            indicate taking a file from the OTA package.)
-    //
-    //            (API v3: this command no longer exists.)
-    //
     //        ui_print <string>
     //            display <string> on the screen.
     //
diff --git a/recovery.cpp b/recovery.cpp
index 9152bb1..0fdc31c 100644
--- a/recovery.cpp
+++ b/recovery.cpp
@@ -170,21 +170,7 @@
  *    -- after this, rebooting will (try to) restart the main system --
  * 7. ** if install failed **
  *    7a. prompt_and_wait() shows an error icon and waits for the user
- *    7b; the user reboots (pulling the battery, etc) into the main system
- * 8. main() calls maybe_install_firmware_update()
- *    ** if the update contained radio/hboot firmware **:
- *    8a. m_i_f_u() writes BCB with "boot-recovery" and "--wipe_cache"
- *        -- after this, rebooting will reformat cache & restart main system --
- *    8b. m_i_f_u() writes firmware image into raw cache partition
- *    8c. m_i_f_u() writes BCB with "update-radio/hboot" and "--wipe_cache"
- *        -- after this, rebooting will attempt to reinstall firmware --
- *    8d. bootloader tries to flash firmware
- *    8e. bootloader writes BCB with "boot-recovery" (keeping "--wipe_cache")
- *        -- after this, rebooting will reformat cache & restart main system --
- *    8f. erase_volume() reformats /cache
- *    8g. finish_recovery() erases BCB
- *        -- after this, rebooting will (try to) restart the main system --
- * 9. main() calls reboot() to boot main system
+ *    7b. the user reboots (pulling the battery, etc) into the main system
  */
 
 // open a given path, mounting partitions as necessary
@@ -513,117 +499,107 @@
     sync();  // For good measure.
 }
 
-typedef struct _saved_log_file {
-    char* name;
-    struct stat st;
-    unsigned char* data;
-    struct _saved_log_file* next;
-} saved_log_file;
+struct saved_log_file {
+  std::string name;
+  struct stat sb;
+  std::string data;
+};
 
 static bool erase_volume(const char* volume) {
-    bool is_cache = (strcmp(volume, CACHE_ROOT) == 0);
-    bool is_data = (strcmp(volume, DATA_ROOT) == 0);
+  bool is_cache = (strcmp(volume, CACHE_ROOT) == 0);
+  bool is_data = (strcmp(volume, DATA_ROOT) == 0);
 
-    ui->SetBackground(RecoveryUI::ERASING);
-    ui->SetProgressType(RecoveryUI::INDETERMINATE);
+  ui->SetBackground(RecoveryUI::ERASING);
+  ui->SetProgressType(RecoveryUI::INDETERMINATE);
 
-    saved_log_file* head = NULL;
+  std::vector<saved_log_file> log_files;
 
-    if (is_cache) {
-        // If we're reformatting /cache, we load any past logs
-        // (i.e. "/cache/recovery/last_*") and the current log
-        // ("/cache/recovery/log") into memory, so we can restore them after
-        // the reformat.
+  if (is_cache) {
+    // If we're reformatting /cache, we load any past logs
+    // (i.e. "/cache/recovery/last_*") and the current log
+    // ("/cache/recovery/log") into memory, so we can restore them after
+    // the reformat.
 
-        ensure_path_mounted(volume);
+    ensure_path_mounted(volume);
 
-        DIR* d;
-        struct dirent* de;
-        d = opendir(CACHE_LOG_DIR);
-        if (d) {
-            char path[PATH_MAX];
-            strcpy(path, CACHE_LOG_DIR);
-            strcat(path, "/");
-            int path_len = strlen(path);
-            while ((de = readdir(d)) != NULL) {
-                if (strncmp(de->d_name, "last_", 5) == 0 || strcmp(de->d_name, "log") == 0) {
-                    saved_log_file* p = (saved_log_file*) malloc(sizeof(saved_log_file));
-                    strcpy(path+path_len, de->d_name);
-                    p->name = strdup(path);
-                    if (stat(path, &(p->st)) == 0) {
-                        // truncate files to 512kb
-                        if (p->st.st_size > (1 << 19)) {
-                            p->st.st_size = 1 << 19;
-                        }
-                        p->data = (unsigned char*) malloc(p->st.st_size);
-                        FILE* f = fopen(path, "rb");
-                        fread(p->data, 1, p->st.st_size, f);
-                        fclose(f);
-                        p->next = head;
-                        head = p;
-                    } else {
-                        free(p);
-                    }
-                }
+    struct dirent* de;
+    std::unique_ptr<DIR, decltype(&closedir)> d(opendir(CACHE_LOG_DIR), closedir);
+    if (d) {
+      while ((de = readdir(d.get())) != nullptr) {
+        if (strncmp(de->d_name, "last_", 5) == 0 || strcmp(de->d_name, "log") == 0) {
+          std::string path = android::base::StringPrintf("%s/%s", CACHE_LOG_DIR, de->d_name);
+
+          struct stat sb;
+          if (stat(path.c_str(), &sb) == 0) {
+            // truncate files to 512kb
+            if (sb.st_size > (1 << 19)) {
+              sb.st_size = 1 << 19;
             }
-            closedir(d);
-        } else {
-            if (errno != ENOENT) {
-                printf("opendir failed: %s\n", strerror(errno));
-            }
+
+            std::string data(sb.st_size, '\0');
+            FILE* f = fopen(path.c_str(), "rb");
+            fread(&data[0], 1, data.size(), f);
+            fclose(f);
+
+            log_files.emplace_back(saved_log_file{ path, sb, data });
+          }
         }
-    }
-
-    ui->Print("Formatting %s...\n", volume);
-
-    ensure_path_unmounted(volume);
-
-    int result;
-
-    if (is_data && reason && strcmp(reason, "convert_fbe") == 0) {
-        // Create convert_fbe breadcrumb file to signal to init
-        // to convert to file based encryption, not full disk encryption
-        if (mkdir(CONVERT_FBE_DIR, 0700) != 0) {
-            ui->Print("Failed to make convert_fbe dir %s\n", strerror(errno));
-            return true;
-        }
-        FILE* f = fopen(CONVERT_FBE_FILE, "wb");
-        if (!f) {
-            ui->Print("Failed to convert to file encryption %s\n", strerror(errno));
-            return true;
-        }
-        fclose(f);
-        result = format_volume(volume, CONVERT_FBE_DIR);
-        remove(CONVERT_FBE_FILE);
-        rmdir(CONVERT_FBE_DIR);
+      }
     } else {
-        result = format_volume(volume);
+      if (errno != ENOENT) {
+        PLOG(ERROR) << "Failed to opendir " << CACHE_LOG_DIR;
+      }
     }
+  }
 
-    if (is_cache) {
-        while (head) {
-            FILE* f = fopen_path(head->name, "wb");
-            if (f) {
-                fwrite(head->data, 1, head->st.st_size, f);
-                fclose(f);
-                chmod(head->name, head->st.st_mode);
-                chown(head->name, head->st.st_uid, head->st.st_gid);
-            }
-            free(head->name);
-            free(head->data);
-            saved_log_file* temp = head->next;
-            free(head);
-            head = temp;
+  ui->Print("Formatting %s...\n", volume);
+
+  ensure_path_unmounted(volume);
+
+  int result;
+
+  if (is_data && reason && strcmp(reason, "convert_fbe") == 0) {
+    // Create convert_fbe breadcrumb file to signal to init
+    // to convert to file based encryption, not full disk encryption
+    if (mkdir(CONVERT_FBE_DIR, 0700) != 0) {
+      ui->Print("Failed to make convert_fbe dir %s\n", strerror(errno));
+      return true;
+    }
+    FILE* f = fopen(CONVERT_FBE_FILE, "wb");
+    if (!f) {
+      ui->Print("Failed to convert to file encryption %s\n", strerror(errno));
+      return true;
+    }
+    fclose(f);
+    result = format_volume(volume, CONVERT_FBE_DIR);
+    remove(CONVERT_FBE_FILE);
+    rmdir(CONVERT_FBE_DIR);
+  } else {
+    result = format_volume(volume);
+  }
+
+  if (is_cache) {
+    // Re-create the log dir and write back the log entries.
+    if (ensure_path_mounted(CACHE_LOG_DIR) == 0 &&
+        dirCreateHierarchy(CACHE_LOG_DIR, 0777, nullptr, false, sehandle) == 0) {
+      for (const auto& log : log_files) {
+        if (!android::base::WriteStringToFile(log.data, log.name, log.sb.st_mode, log.sb.st_uid,
+                                              log.sb.st_gid)) {
+          PLOG(ERROR) << "Failed to write to " << log.name;
         }
-
-        // Any part of the log we'd copied to cache is now gone.
-        // Reset the pointer so we copy from the beginning of the temp
-        // log.
-        tmplog_offset = 0;
-        copy_logs();
+      }
+    } else {
+      PLOG(ERROR) << "Failed to mount / create " << CACHE_LOG_DIR;
     }
 
-    return (result == 0);
+    // Any part of the log we'd copied to cache is now gone.
+    // Reset the pointer so we copy from the beginning of the temp
+    // log.
+    tmplog_offset = 0;
+    copy_logs();
+  }
+
+  return (result == 0);
 }
 
 static int
diff --git a/tests/Android.mk b/tests/Android.mk
index 5f6a7ce..1621f37 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -62,8 +62,10 @@
 LOCAL_SRC_FILES := \
     component/applypatch_test.cpp \
     component/edify_test.cpp \
+    component/uncrypt_test.cpp \
     component/updater_test.cpp \
     component/verifier_test.cpp
+
 LOCAL_FORCE_STATIC_EXECUTABLE := true
 
 tune2fs_static_libraries := \
diff --git a/tests/component/uncrypt_test.cpp b/tests/component/uncrypt_test.cpp
new file mode 100644
index 0000000..a554c3e
--- /dev/null
+++ b/tests/component/uncrypt_test.cpp
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2016 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 <arpa/inet.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/un.h>
+#include <unistd.h>
+
+#include <string>
+
+#include <android-base/file.h>
+#include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/unique_fd.h>
+#include <bootloader_message/bootloader_message.h>
+#include <gtest/gtest.h>
+
+static const std::string UNCRYPT_SOCKET = "/dev/socket/uncrypt";
+static const std::string INIT_SVC_SETUP_BCB = "init.svc.setup-bcb";
+static const std::string INIT_SVC_CLEAR_BCB = "init.svc.clear-bcb";
+static const std::string INIT_SVC_UNCRYPT = "init.svc.uncrypt";
+static constexpr int SOCKET_CONNECTION_MAX_RETRY = 30;
+
+class UncryptTest : public ::testing::Test {
+ protected:
+  virtual void SetUp() {
+    ASSERT_TRUE(android::base::SetProperty("ctl.stop", "setup-bcb"));
+    ASSERT_TRUE(android::base::SetProperty("ctl.stop", "clear-bcb"));
+    ASSERT_TRUE(android::base::SetProperty("ctl.stop", "uncrypt"));
+
+    bool success = false;
+    for (int retry = 0; retry < SOCKET_CONNECTION_MAX_RETRY; retry++) {
+      std::string setup_bcb = android::base::GetProperty(INIT_SVC_SETUP_BCB, "");
+      std::string clear_bcb = android::base::GetProperty(INIT_SVC_CLEAR_BCB, "");
+      std::string uncrypt = android::base::GetProperty(INIT_SVC_UNCRYPT, "");
+      LOG(INFO) << "setup-bcb: [" << setup_bcb << "] clear-bcb: [" << clear_bcb << "] uncrypt: ["
+                << uncrypt << "]";
+      if (setup_bcb != "running" && clear_bcb != "running" && uncrypt != "running") {
+        success = true;
+        break;
+      }
+      sleep(1);
+    }
+
+    ASSERT_TRUE(success) << "uncrypt service is not available.";
+  }
+};
+
+TEST_F(UncryptTest, setup_bcb) {
+  // Trigger the setup-bcb service.
+  ASSERT_TRUE(android::base::SetProperty("ctl.start", "setup-bcb"));
+
+  // Test tends to be flaky if proceeding immediately ("Transport endpoint is not connected").
+  sleep(1);
+
+  struct sockaddr_un un = {};
+  un.sun_family = AF_UNIX;
+  strlcpy(un.sun_path, UNCRYPT_SOCKET.c_str(), sizeof(un.sun_path));
+
+  int sockfd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
+  ASSERT_NE(-1, sockfd);
+
+  // Connect to the uncrypt socket.
+  bool success = false;
+  for (int retry = 0; retry < SOCKET_CONNECTION_MAX_RETRY; retry++) {
+    if (connect(sockfd, reinterpret_cast<struct sockaddr*>(&un), sizeof(struct sockaddr_un)) != 0) {
+      success = true;
+      break;
+    }
+    sleep(1);
+  }
+  ASSERT_TRUE(success);
+
+  // Send out the BCB message.
+  std::string message = "--update_message=abc value";
+  std::string message_in_bcb = "recovery\n--update_message=abc value\n";
+  int length = static_cast<int>(message.size());
+  int length_out = htonl(length);
+  ASSERT_TRUE(android::base::WriteFully(sockfd, &length_out, sizeof(int)))
+      << "Failed to write length: " << strerror(errno);
+  ASSERT_TRUE(android::base::WriteFully(sockfd, message.data(), length))
+      << "Failed to write message: " << strerror(errno);
+
+  // Check the status code from uncrypt.
+  int status;
+  ASSERT_TRUE(android::base::ReadFully(sockfd, &status, sizeof(int)));
+  ASSERT_EQ(100U, ntohl(status));
+
+  // Ack having received the status code.
+  int code = 0;
+  ASSERT_TRUE(android::base::WriteFully(sockfd, &code, sizeof(int)));
+
+  ASSERT_EQ(0, close(sockfd));
+
+  ASSERT_TRUE(android::base::SetProperty("ctl.stop", "setup-bcb"));
+
+  // Verify the message by reading from BCB directly.
+  bootloader_message boot;
+  std::string err;
+  ASSERT_TRUE(read_bootloader_message(&boot, &err)) << "Failed to read BCB: " << err;
+
+  ASSERT_EQ("boot-recovery", std::string(boot.command));
+  ASSERT_EQ(message_in_bcb, std::string(boot.recovery));
+
+  // The rest of the boot.recovery message should be zero'd out.
+  ASSERT_LE(message_in_bcb.size(), sizeof(boot.recovery));
+  size_t left = sizeof(boot.recovery) - message_in_bcb.size();
+  ASSERT_EQ(std::string(left, '\0'), std::string(&boot.recovery[message_in_bcb.size()], left));
+
+  // Clear the BCB.
+  ASSERT_TRUE(clear_bootloader_message(&err)) << "Failed to clear BCB: " << err;
+}
+
+TEST_F(UncryptTest, clear_bcb) {
+  // Trigger the clear-bcb service.
+  ASSERT_TRUE(android::base::SetProperty("ctl.start", "clear-bcb"));
+
+  // Test tends to be flaky if proceeding immediately ("Transport endpoint is not connected").
+  sleep(1);
+
+  struct sockaddr_un un = {};
+  un.sun_family = AF_UNIX;
+  strlcpy(un.sun_path, UNCRYPT_SOCKET.c_str(), sizeof(un.sun_path));
+
+  int sockfd = socket(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0);
+  ASSERT_NE(-1, sockfd);
+
+  // Connect to the uncrypt socket.
+  bool success = false;
+  for (int retry = 0; retry < SOCKET_CONNECTION_MAX_RETRY; retry++) {
+    if (connect(sockfd, reinterpret_cast<struct sockaddr*>(&un), sizeof(struct sockaddr_un)) != 0) {
+      success = true;
+      break;
+    }
+    sleep(1);
+  }
+  ASSERT_TRUE(success);
+
+  // Check the status code from uncrypt.
+  int status;
+  ASSERT_TRUE(android::base::ReadFully(sockfd, &status, sizeof(int)));
+  ASSERT_EQ(100U, ntohl(status));
+
+  // Ack having received the status code.
+  int code = 0;
+  ASSERT_TRUE(android::base::WriteFully(sockfd, &code, sizeof(int)));
+
+  ASSERT_EQ(0, close(sockfd));
+
+  ASSERT_TRUE(android::base::SetProperty("ctl.stop", "clear-bcb"));
+
+  // Verify the content by reading from BCB directly.
+  bootloader_message boot;
+  std::string err;
+  ASSERT_TRUE(read_bootloader_message(&boot, &err)) << "Failed to read BCB: " << err;
+
+  // All the bytes should be cleared.
+  ASSERT_EQ(std::string(sizeof(boot), '\0'),
+            std::string(reinterpret_cast<const char*>(&boot), sizeof(boot)));
+}
diff --git a/uncrypt/uncrypt.cpp b/uncrypt/uncrypt.cpp
index 38b25ab..4ac516d 100644
--- a/uncrypt/uncrypt.cpp
+++ b/uncrypt/uncrypt.cpp
@@ -530,7 +530,7 @@
     std::string content;
     content.resize(length);
     if (!android::base::ReadFully(socket, &content[0], length)) {
-        PLOG(ERROR) << "failed to read the length";
+        PLOG(ERROR) << "failed to read the message";
         return false;
     }
     LOG(INFO) << "  received command: [" << content << "] (" << content.size() << ")";
diff --git a/updater/blockimg.cpp b/updater/blockimg.cpp
index 7257e23..4dadceb 100644
--- a/updater/blockimg.cpp
+++ b/updater/blockimg.cpp
@@ -1358,7 +1358,7 @@
     CommandParameters params = {};
     params.canwrite = !dryrun;
 
-    LOG(INFO) << "performing " << dryrun ? "verification" : "update";
+    LOG(INFO) << "performing " << (dryrun ? "verification" : "update");
     if (state->is_retry) {
         is_retry = true;
         LOG(INFO) << "This update is a retry.";