Reboot and retry on I/O errors

When I/O error happens, reboot and retry installation two times
before we abort this OTA update.

Bug: 25633753
Change-Id: Iba6d4203a343a725aa625a41d237606980d62f69
(cherry picked from commit 3c62b67faf8a25f1dd1c44dc19759c3997fdfd36)
diff --git a/install.cpp b/install.cpp
index c0d0077..8a82d7b 100644
--- a/install.cpp
+++ b/install.cpp
@@ -144,6 +144,7 @@
     close(pipefd[1]);
 
     *wipe_cache = false;
+    bool retry_update = false;
 
     char buffer[1024];
     FILE* from_child = fdopen(pipefd[0], "r");
@@ -180,6 +181,8 @@
             // to be able to reboot during installation (useful for
             // debugging packages that don't exit).
             ui->SetEnableReboot(true);
+        } else if (strcmp(command, "retry_update") == 0) {
+            retry_update = true;
         } else {
             LOGE("unknown command [%s]\n", command);
         }
@@ -188,6 +191,9 @@
 
     int status;
     waitpid(pid, &status, 0);
+    if (retry_update) {
+        return INSTALL_RETRY;
+    }
     if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
         LOGE("Error in %s\n(Status %d)\n", path, WEXITSTATUS(status));
         return INSTALL_ERROR;
diff --git a/install.h b/install.h
index f92f061..fd08e3c 100644
--- a/install.h
+++ b/install.h
@@ -23,7 +23,8 @@
 extern "C" {
 #endif
 
-enum { INSTALL_SUCCESS, INSTALL_ERROR, INSTALL_CORRUPT, INSTALL_NONE, INSTALL_SKIPPED };
+enum { INSTALL_SUCCESS, INSTALL_ERROR, INSTALL_CORRUPT, INSTALL_NONE, INSTALL_SKIPPED,
+        INSTALL_RETRY };
 // Install the package specified by root_path.  If INSTALL_SUCCESS is
 // returned and *wipe_cache is true on exit, caller should wipe the
 // cache partition.
diff --git a/otafault/ota_io.cpp b/otafault/ota_io.cpp
index 02e80f9..a378040 100644
--- a/otafault/ota_io.cpp
+++ b/otafault/ota_io.cpp
@@ -38,6 +38,8 @@
 #endif // defined (TARGET_READ_FAULT)
 #endif // defined (TARGET_INJECT_FAULTS)
 
+bool have_eio_error = false;
+
 int ota_open(const char* path, int oflags) {
 #if defined (TARGET_INJECT_FAULTS)
     // Let the caller handle errors; we do not care if open succeeds or fails
@@ -90,12 +92,22 @@
             && FilenameCache[(intptr_t)stream] == FaultFileName) {
         FaultFileName = "";
         errno = EIO;
+        have_eio_error = true;
         return 0;
     } else {
-        return fread(ptr, size, nitems, stream);
+        size_t status = fread(ptr, size, nitems, stream);
+        // If I/O error occurs, set the retry-update flag.
+        if (status != nitems && errno == EIO) {
+            have_eio_error = true;
+        }
+        return status;
     }
 #else
-    return fread(ptr, size, nitems, stream);
+    size_t status = fread(ptr, size, nitems, stream);
+    if (status != nitems && errno == EIO) {
+        have_eio_error = true;
+    }
+    return status;
 #endif
 }
 
@@ -105,12 +117,21 @@
             && FilenameCache[fd] == FaultFileName) {
         FaultFileName = "";
         errno = EIO;
+        have_eio_error = true;
         return -1;
     } else {
-        return read(fd, buf, nbyte);
+        ssize_t status = read(fd, buf, nbyte);
+        if (status == -1 && errno == EIO) {
+            have_eio_error = true;
+        }
+        return status;
     }
 #else
-    return read(fd, buf, nbyte);
+    ssize_t status = read(fd, buf, nbyte);
+    if (status == -1 && errno == EIO) {
+        have_eio_error = true;
+    }
+    return status;
 #endif
 }
 
@@ -120,12 +141,21 @@
             && FilenameCache[(intptr_t)stream] == FaultFileName) {
         FaultFileName = "";
         errno = EIO;
+        have_eio_error = true;
         return 0;
     } else {
-        return fwrite(ptr, size, count, stream);
+        size_t status = fwrite(ptr, size, count, stream);
+        if (status != count && errno == EIO) {
+            have_eio_error = true;
+        }
+        return status;
     }
 #else
-    return fwrite(ptr, size, count, stream);
+    size_t status = fwrite(ptr, size, count, stream);
+    if (status != count && errno == EIO) {
+        have_eio_error = true;
+    }
+    return status;
 #endif
 }
 
@@ -135,12 +165,21 @@
             && FilenameCache[fd] == FaultFileName) {
         FaultFileName = "";
         errno = EIO;
+        have_eio_error = true;
         return -1;
     } else {
-        return write(fd, buf, nbyte);
+        ssize_t status = write(fd, buf, nbyte);
+        if (status == -1 && errno == EIO) {
+            have_eio_error = true;
+        }
+        return status;
     }
 #else
-    return write(fd, buf, nbyte);
+    ssize_t status = write(fd, buf, nbyte);
+    if (status == -1 && errno == EIO) {
+        have_eio_error = true;
+    }
+    return status;
 #endif
 }
 
@@ -151,10 +190,19 @@
         FaultFileName = "";
         errno = EIO;
         return -1;
+        have_eio_error = true;
     } else {
-        return fsync(fd);
+        int status = fsync(fd);
+        if (status == -1 && errno == EIO) {
+            have_eio_error = true;
+        }
+        return status;
     }
 #else
-    return fsync(fd);
+    int status = fsync(fd);
+    if (status == -1 && errno == EIO) {
+        have_eio_error = true;
+    }
+    return status;
 #endif
 }
diff --git a/recovery.cpp b/recovery.cpp
index fe6793d..7620f1a 100644
--- a/recovery.cpp
+++ b/recovery.cpp
@@ -36,6 +36,7 @@
 
 #include <adb.h>
 #include <android-base/file.h>
+#include <android-base/parseint.h>
 #include <android-base/stringprintf.h>
 #include <cutils/android_reboot.h>
 #include <cutils/properties.h>
@@ -60,6 +61,7 @@
 static const struct option OPTIONS[] = {
   { "send_intent", required_argument, NULL, 'i' },
   { "update_package", required_argument, NULL, 'u' },
+  { "retry_count", required_argument, NULL, 'n' },
   { "wipe_data", no_argument, NULL, 'w' },
   { "wipe_cache", no_argument, NULL, 'c' },
   { "show_text", no_argument, NULL, 't' },
@@ -89,6 +91,7 @@
 static const char *LAST_KMSG_FILE = "/cache/recovery/last_kmsg";
 static const char *LAST_LOG_FILE = "/cache/recovery/last_log";
 static const int KEEP_LOG_COUNT = 10;
+static const int EIO_RETRY_COUNT = 2;
 static const int BATTERY_READ_TIMEOUT_IN_SEC = 10;
 // GmsCore enters recovery mode to install package when having enough battery
 // percentage. Normally, the threshold is 40% without charger and 20% with charger.
@@ -1162,6 +1165,29 @@
     }
 }
 
+static void set_retry_bootloader_message(int retry_count, int argc, char** argv) {
+    struct bootloader_message boot {};
+    strlcpy(boot.command, "boot-recovery", sizeof(boot.command));
+    strlcpy(boot.recovery, "recovery\n", sizeof(boot.recovery));
+
+    for (int i = 1; i < argc; ++i) {
+        if (strstr(argv[i], "retry_count") == nullptr) {
+            strlcat(boot.recovery, argv[i], sizeof(boot.recovery));
+            strlcat(boot.recovery, "\n", sizeof(boot.recovery));
+        }
+    }
+
+    // Initialize counter to 1 if it's not in BCB, otherwise increment it by 1.
+    if (retry_count == 0) {
+        strlcat(boot.recovery, "--retry_count=1\n", sizeof(boot.recovery));
+    } else {
+        char buffer[20];
+        snprintf(buffer, sizeof(buffer), "--retry_count=%d\n", retry_count+1);
+        strlcat(boot.recovery, buffer, sizeof(boot.recovery));
+    }
+    set_bootloader_message(&boot);
+}
+
 int main(int argc, char **argv) {
     // If this binary is started with the single argument "--adbd",
     // instead of being the normal recovery binary, it turns into kind
@@ -1197,11 +1223,13 @@
     bool sideload_auto_reboot = false;
     bool just_exit = false;
     bool shutdown_after = false;
+    int retry_count = 0;
 
     int arg;
     while ((arg = getopt_long(argc, argv, "", OPTIONS, NULL)) != -1) {
         switch (arg) {
         case 'i': send_intent = optarg; break;
+        case 'n': android::base::ParseInt(optarg, &retry_count, 0); break;
         case 'u': update_package = optarg; break;
         case 'w': should_wipe_data = true; break;
         case 'c': should_wipe_cache = true; break;
@@ -1306,7 +1334,24 @@
             }
             if (status != INSTALL_SUCCESS) {
                 ui->Print("Installation aborted.\n");
+                // When I/O error happens, reboot and retry installation EIO_RETRY_COUNT
+                // times before we abandon this OTA update.
+                if (status == INSTALL_RETRY && retry_count < EIO_RETRY_COUNT) {
+                    copy_logs();
+                    set_retry_bootloader_message(retry_count, argc, argv);
+                    // Print retry count on screen.
+                    ui->Print("Retry attempt %d\n", retry_count);
 
+                    // Reboot and retry the update
+                    int ret = property_set(ANDROID_RB_PROPERTY, "reboot,recovery");
+                    if (ret < 0) {
+                        ui->Print("Reboot failed\n");
+                    } else {
+                        while (true) {
+                            pause();
+                        }
+                    }
+                }
                 // If this is an eng or userdebug build, then automatically
                 // turn the text display on if the script fails so the error
                 // message is visible.
diff --git a/updater/updater.cpp b/updater/updater.cpp
index 0f22e6d..ddc01e1 100644
--- a/updater/updater.cpp
+++ b/updater/updater.cpp
@@ -35,6 +35,8 @@
 // (Note it's "updateR-script", not the older "update-script".)
 #define SCRIPT_NAME "META-INF/com/google/android/updater-script"
 
+extern bool have_eio_error;
+
 struct selabel_handle *sehandle;
 
 int main(int argc, char** argv) {
@@ -139,6 +141,11 @@
     state.errmsg = NULL;
 
     char* result = Evaluate(&state, root);
+
+    if (have_eio_error) {
+        fprintf(cmd_pipe, "retry_update\n");
+    }
+
     if (result == NULL) {
         if (state.errmsg == NULL) {
             printf("script aborted (no error message)\n");