save the recovery log from before HTC firmware updates

When doing a firmware (radio or hboot) update on HTC devices, save the
recovery log in block 1 of the cache partition, before the firmware
image and the UI bitmaps.  When we boot back into recovery after the
firmware update to reformat the cache partition, copy that log out of
cache before reformatting it and dump it into the current invocation's
log.

The practical upshot of all this is that we can see the log output
from radio and hboot updates.

Change-Id: Ie0e89566754c88f4bed6a90d8a0aa04047b01a27
diff --git a/bootloader.c b/bootloader.c
index bc79bee..61b24e9 100644
--- a/bootloader.c
+++ b/bootloader.c
@@ -155,10 +155,14 @@
     unsigned fail_bitmap_length;
 };
 
+#define LOG_MAGIC        "LOGmagic"
+#define LOG_MAGIC_SIZE   8
+
 int write_update_for_bootloader(
         const char *update, int update_length,
         int bitmap_width, int bitmap_height, int bitmap_bpp,
-        const char *busy_bitmap, const char *fail_bitmap) {
+        const char *busy_bitmap, const char *fail_bitmap,
+        const char *log_filename) {
     if (ensure_root_path_unmounted(CACHE_NAME)) {
         LOGE("Can't unmount %s\n", CACHE_NAME);
         return -1;
@@ -198,6 +202,21 @@
     header.version = UPDATE_VERSION;
     header.size = header_size;
 
+    if (log_filename != NULL) {
+        // Write 1 byte into the following block, then fill to the end
+        // in order to reserve that block.  We'll use the block to
+        // send a copy of the log through to the next invocation of
+        // recovery.  We write the log as late as possible in order to
+        // capture any messages emitted by this function.
+        mtd_erase_blocks(write, 0);
+        if (mtd_write_data(write, (char*) &header, 1) != 1) {
+            LOGE("Can't write log block to %s\n(%s)\n",
+                 CACHE_NAME, strerror(errno));
+            mtd_write_close(write);
+            return -1;
+        }
+    }
+
     off_t image_start_pos = mtd_erase_blocks(write, 0);
     header.image_length = update_length;
     if ((int) header.image_offset == -1 ||
@@ -256,6 +275,37 @@
         return -1;
     }
 
+    if (log_filename != NULL) {
+        size_t erase_size;
+        if (mtd_partition_info(part, NULL, &erase_size, NULL) != 0) {
+            LOGE("Error reading block size\n(%s)\n", strerror(errno));
+            mtd_write_close(write);
+            return -1;
+        }
+        mtd_erase_blocks(write, 0);
+
+        if (erase_size > 0) {
+            char* log = malloc(erase_size);
+            FILE* f = fopen(log_filename, "rb");
+            // The fseek() may fail if it tries to go before the
+            // beginning of the log, but that's okay because we want
+            // to be positioned at the start anyway.
+            fseek(f, -(erase_size-sizeof(size_t)-LOG_MAGIC_SIZE), SEEK_END);
+            memcpy(log, LOG_MAGIC, LOG_MAGIC_SIZE);
+            size_t read = fread(log+sizeof(size_t)+LOG_MAGIC_SIZE,
+                                1, erase_size-sizeof(size_t)-LOG_MAGIC_SIZE, f);
+            LOGI("read %d bytes from log\n", (int)read);
+            *(size_t *)(log + LOG_MAGIC_SIZE) = read;
+            fclose(f);
+            if (mtd_write_data(write, log, erase_size) != erase_size) {
+                LOGE("failed to store log in cache partition\n(%s)\n",
+                     strerror(errno));
+                mtd_write_close(write);
+            }
+            free(log);
+        }
+    }
+
     if (mtd_erase_blocks(write, 0) != image_start_pos) {
         LOGE("Misalignment rewriting %s\n(%s)\n", CACHE_NAME, strerror(errno));
         mtd_write_close(write);
@@ -269,3 +319,52 @@
 
     return 0;
 }
+
+void recover_firmware_update_log() {
+    printf("recovering log from before firmware update\n");
+
+    const MtdPartition *part = get_root_mtd_partition(CACHE_NAME);
+    if (part == NULL) {
+        LOGE("Can't find %s\n", CACHE_NAME);
+        return;
+    }
+
+    MtdReadContext* read = mtd_read_partition(part);
+
+    size_t erase_size;
+    if (mtd_partition_info(part, NULL, &erase_size, NULL) != 0) {
+        LOGE("Error reading block size\n(%s)\n", strerror(errno));
+        mtd_read_close(read);
+        return;
+    }
+
+    char* buffer = malloc(erase_size);
+    if (mtd_read_data(read, buffer, erase_size) != erase_size) {
+        LOGE("Error reading header block\n(%s)\n", strerror(errno));
+        mtd_read_close(read);
+        free(buffer);
+        return;
+    }
+    if (mtd_read_data(read, buffer, erase_size) != erase_size) {
+        LOGE("Error reading log block\n(%s)\n", strerror(errno));
+        mtd_read_close(read);
+        free(buffer);
+        return;
+    }
+    mtd_read_close(read);
+
+    if (memcmp(buffer, LOG_MAGIC, LOG_MAGIC_SIZE) != 0) {
+        LOGE("No log from before firmware install\n");
+        free(buffer);
+        return;
+    }
+
+    size_t log_size = *(size_t *)(buffer + LOG_MAGIC_SIZE);
+    LOGI("header has %d bytes of log\n", (int)log_size);
+
+    printf("\n###\n### START RECOVERED LOG\n###\n\n");
+    fwrite(buffer + sizeof(size_t) + LOG_MAGIC_SIZE, 1, log_size, stdout);
+    printf("\n\n###\n### END RECOVERED LOG\n###\n\n");
+
+    free(buffer);
+}
diff --git a/bootloader.h b/bootloader.h
index 3d4302f..fec6409 100644
--- a/bootloader.h
+++ b/bootloader.h
@@ -54,6 +54,13 @@
 int write_update_for_bootloader(
         const char *update, int update_len,
         int bitmap_width, int bitmap_height, int bitmap_bpp,
-        const char *busy_bitmap, const char *error_bitmap);
+        const char *busy_bitmap, const char *error_bitmap,
+        const char *log_filename);
+
+/* Look for a log stored in the cache partition in the block after the
+ * firmware update header.  If we can read such a log, copy it to
+ * stdout (ie, the current log).
+ */
+void recover_firmware_update_log();
 
 #endif
diff --git a/firmware.c b/firmware.c
index e2e4fe6..6739c1e 100644
--- a/firmware.c
+++ b/firmware.c
@@ -76,7 +76,8 @@
  * It is recovery's responsibility to clean up the mess afterwards.
  */
 
-int maybe_install_firmware_update(const char *send_intent) {
+int maybe_install_firmware_update(const char *send_intent,
+                                  const char *log_filename) {
     if (update_data == NULL || update_length == 0) return 0;
 
     /* We destroy the cache partition to pass the update image to the
@@ -104,7 +105,7 @@
     ui_print("Writing %s image...\n", update_type);
     if (write_update_for_bootloader(
             update_data, update_length,
-            width, height, bpp, busy_image, fail_image)) {
+            width, height, bpp, busy_image, fail_image, log_filename)) {
         LOGE("Can't write %s image\n(%s)\n", update_type, strerror(errno));
         format_root_device("CACHE:");  // Attempt to clean cache up, at least.
         return -1;
@@ -118,6 +119,7 @@
      * wipe the cache and reboot into the system.)
      */
     snprintf(boot.command, sizeof(boot.command), "update-%s", update_type);
+    strlcat(boot.recovery, "--recover_log\n", sizeof(boot.recovery));
     if (set_bootloader_message(&boot)) {
         format_root_device("CACHE:");
         return -1;
diff --git a/firmware.h b/firmware.h
index aeb8f97..04507bb 100644
--- a/firmware.h
+++ b/firmware.h
@@ -30,6 +30,7 @@
  * Returns 0 if no radio image was defined, nonzero on error,
  * doesn't return at all on success...
  */
-int maybe_install_firmware_update(const char *send_intent);
+int maybe_install_firmware_update(const char *send_intent,
+                                  const char *log_filename);
 
 #endif
diff --git a/recovery.c b/recovery.c
index 58c84ef..1a88560 100644
--- a/recovery.c
+++ b/recovery.c
@@ -46,6 +46,7 @@
   { "wipe_cache", no_argument, NULL, 'c' },
   // TODO{oam}: implement improved command line passing key, egnot to review.
   { "set_encrypted_filesystem", required_argument, NULL, 'e' },
+  { "recover_log", no_argument, NULL, 'g' },
   { NULL, 0, NULL, 0 },
 };
 
@@ -491,6 +492,7 @@
         case 'w': wipe_data = wipe_cache = 1; break;
         case 'c': wipe_cache = 1; break;
         case 'e': efs_mode = optarg; toggle_efs = 1; break;
+        case 'g': recover_firmware_update_log(); break;
         case '?':
             LOGE("Invalid command argument\n");
             continue;
@@ -562,7 +564,7 @@
     if (status != INSTALL_SUCCESS || ui_text_visible()) prompt_and_wait();
 
     // If there is a radio image pending, reboot now to install it.
-    maybe_install_firmware_update(send_intent);
+    maybe_install_firmware_update(send_intent, TEMPORARY_LOG_FILE);
 
     // Otherwise, get ready to boot the main system...
     finish_recovery(send_intent);