From 651b4fe420f35d8a690fa78a06ff79f9ffc955a2 Mon Sep 17 00:00:00 2001
From: yuying-y <78829091+yuying-y@users.noreply.github.com>
Date: Thu, 18 Jul 2024 18:08:10 -0700
Subject: [PATCH] Make loader app load updated system image directly (#3421)

b/344069914
b/328421395
---
 starboard/loader_app/BUILD.gn                |   5 +
 starboard/loader_app/slot_management.cc      | 147 ++++++++++++++++++-
 starboard/loader_app/slot_management_test.cc |  96 ++++++++++++
 3 files changed, 247 insertions(+), 1 deletion(-)

diff --git a/starboard/loader_app/BUILD.gn b/starboard/loader_app/BUILD.gn
index cc0bded25243..a8891dbc6723 100644
--- a/starboard/loader_app/BUILD.gn
+++ b/starboard/loader_app/BUILD.gn
@@ -293,6 +293,7 @@ static_library("slot_management") {
     "//starboard/elf_loader",
     "//starboard/elf_loader:constants",
     "//starboard/elf_loader:sabi_string",
+    "//third_party/jsoncpp:jsoncpp",
   ]
 
   if (sb_is_evergreen_compatible && current_toolchain == starboard_toolchain) {
@@ -300,6 +301,8 @@ static_library("slot_management") {
   } else {
     deps += [ "//third_party/crashpad/crashpad/wrapper:wrapper_stub" ]
   }
+
+  configs += [ "//third_party/jsoncpp:jsoncpp_config" ]
 }
 if (sb_is_evergreen) {
   target(starboard_level_gtest_target_type, "slot_management_test") {
@@ -320,7 +323,9 @@ if (sb_is_evergreen) {
       "//starboard/elf_loader:sabi_string",
       "//testing/gmock",
       "//testing/gtest",
+      "//third_party/jsoncpp:jsoncpp",
     ]
+    configs += [ "//third_party/jsoncpp:jsoncpp_config" ]
   }
 }
 
diff --git a/starboard/loader_app/slot_management.cc b/starboard/loader_app/slot_management.cc
index c576e565ed4c..c56a81bee7bf 100644
--- a/starboard/loader_app/slot_management.cc
+++ b/starboard/loader_app/slot_management.cc
@@ -14,10 +14,16 @@
 
 #include "starboard/loader_app/slot_management.h"
 
+#include <stdio.h>
 #include <sys/stat.h>
 
+#include <algorithm>
+#include <iostream>
+#include <sstream>
+#include <string>
 #include <vector>
 
+#include "starboard/common/file.h"
 #include "starboard/common/log.h"
 #include "starboard/common/string.h"
 #include "starboard/configuration_constants.h"
@@ -33,11 +39,16 @@
 #include "starboard/string.h"
 #include "third_party/crashpad/crashpad/wrapper/annotations.h"
 #include "third_party/crashpad/crashpad/wrapper/wrapper.h"
+#include "third_party/jsoncpp/source/include/json/reader.h"
+#include "third_party/jsoncpp/source/include/json/value.h"
 
 namespace starboard {
 namespace loader_app {
 namespace {
 
+// The max length of Evergreen version string.
+const int kMaxEgVersionLength = 20;
+
 // The max number of installations slots.
 const int kMaxNumInstallations = 3;
 
@@ -54,8 +65,113 @@ const char kCompressedCobaltLibraryName[] = "libcobalt.lz4";
 // the Cobalt installation.
 const char kCobaltContentPath[] = "content";
 
+// Filename for the manifest file which contains the Evergreen version.
+const char kManifestFileName[] = "manifest.json";
+
+// Deliminator of the Evergreen version string segments.
+const char kEgVersionDeliminator = '.';
+
+// Evergreen version key in the manifest file.
+const char kVersionKey[] = "version";
+
 }  // namespace
 
+// Compares the Evergreen versions v1 and v2. Returns 1 if v1 is newer than v2;
+// returns -1 if v1 is older than v2; returns 0 if v1 is the same as v2, or if
+// either of them is invalid.
+int CompareEvergreenVersion(std::vector<char>* v1, std::vector<char>* v2) {
+  if ((*v1)[0] == '\0' || (*v2)[0] == '\0') {
+    return 0;
+  }
+
+  // Split the version strings into segments of numbers
+  std::vector<int> n1, n2;
+  std::stringstream s1(std::string(v1->begin(), v1->end()));
+  std::stringstream s2(std::string(v2->begin(), v2->end()));
+  std::string seg;
+  while (std::getline(s1, seg, kEgVersionDeliminator)) {
+    n1.push_back(std::stoi(seg));
+  }
+  while (std::getline(s2, seg, kEgVersionDeliminator)) {
+    n2.push_back(std::stoi(seg));
+  }
+
+  // Compare each segment
+  int size = std::min(n1.size(), n2.size());
+  for (int i = 0; i < size; i++) {
+    if (n1[i] > n2[i]) {
+      return 1;
+    } else if (n1[i] < n2[i]) {
+      return -1;
+    }
+  }
+
+  // If all segments are equal, compare the lengths
+  if (n1.size() > n2.size()) {
+    return 1;
+  } else if (n1.size() < n2.size()) {
+    return -1;
+  }
+  return 0;
+}
+
+// Reads the Evergreen version from the manifest file at the
+// |manifest_file_path|, and stores in |version|.
+bool ReadEvergreenVersion(std::vector<char>* manifest_file_path,
+                          char* version,
+                          int version_length) {
+  // Check the manifest file exists
+  struct stat info;
+  if (stat(manifest_file_path->data(), &info) != 0) {
+    SB_LOG(WARNING)
+        << "Failed to open the manifest file at the installation path.";
+    return false;
+  }
+
+  ScopedFile manifest_file(manifest_file_path->data(), O_RDONLY,
+                           S_IRWXU | S_IRGRP);
+  int64_t file_size = manifest_file.GetSize();
+  std::vector<char> file_data(file_size);
+  int read_size = manifest_file.ReadAll(file_data.data(), file_size);
+  if (read_size < 0) {
+    SB_LOG(WARNING) << "Error while reading from the manifest file.";
+    return false;
+  }
+
+  Json::Reader reader;
+  Json::Value obj;
+  if (!reader.parse(std::string(file_data.data()), obj) || !obj[kVersionKey]) {
+    SB_LOG(WARNING) << "Failed to parse version from the manifest file at the "
+                       "installation path.";
+    return false;
+  }
+
+  snprintf(version, version_length, "%s", obj[kVersionKey].asString().c_str());
+  return true;
+}
+
+bool GetEvergreenVersionByIndex(int installation_index,
+                                char* version,
+                                int version_length) {
+  std::vector<char> installation_path(kSbFileMaxPath);
+  if (ImGetInstallationPath(installation_index, installation_path.data(),
+                            kSbFileMaxPath) == IM_ERROR) {
+    SB_LOG(ERROR) << "Failed to get installation path of installation index "
+                  << installation_index;
+    return false;
+  }
+  std::vector<char> manifest_file_path(kSbFileMaxPath);
+  snprintf(manifest_file_path.data(), kSbFileMaxPath, "%s%s%s",
+           installation_path.data(), kSbFileSepString, kManifestFileName);
+  if (!ReadEvergreenVersion(&manifest_file_path, version, version_length)) {
+    SB_LOG(WARNING)
+        << "Failed to read the Evergreen version of installation index "
+        << installation_index;
+    return false;
+  }
+  return true;
+}
+
 int RevertBack(int current_installation,
                const std::string& app_key,
                bool mark_bad,
@@ -148,9 +264,38 @@ void* LoadSlotManagedLibrary(const std::string& app_key,
     SB_LOG(WARNING) << "Failed to roll forward";
   }
 
+  int current_installation = ImGetCurrentInstallationIndex();
+
+  // Check the system image. If it's newer than the current slot, update to
+  // system image immediately.
+  if (current_installation != 0) {
+    std::vector<char> current_version(kMaxEgVersionLength);
+    if (!GetEvergreenVersionByIndex(current_installation,
+                                    current_version.data(),
+                                    kMaxEgVersionLength)) {
+      SB_LOG(WARNING)
+          << "Failed to get the Evergreen version of installation index "
+          << current_installation;
+    }
+
+    std::vector<char> system_image_version(kMaxEgVersionLength);
+    if (!GetEvergreenVersionByIndex(0, system_image_version.data(),
+                                    kMaxEgVersionLength)) {
+      SB_LOG(WARNING)
+          << "Failed to get the Evergreen version of installation index " << 0;
+    }
+
+    if (CompareEvergreenVersion(&system_image_version, &current_version) > 0) {
+      if (ImRollForward(0) != IM_ERROR) {
+        current_installation = 0;
+      } else {
+        SB_LOG(WARNING) << "Failed to roll forward to system image";
+      }
+    }
+  }
+
   // TODO: Try to simplify the loop.
   // Loop by priority.
-  int current_installation = ImGetCurrentInstallationIndex();
   while (current_installation != IM_ERROR) {
     // if not successful and num_tries_left > 0 decrement and try to
     // load the library.
diff --git a/starboard/loader_app/slot_management_test.cc b/starboard/loader_app/slot_management_test.cc
index 25c993db78db..e867175d2c1e 100644
--- a/starboard/loader_app/slot_management_test.cc
+++ b/starboard/loader_app/slot_management_test.cc
@@ -14,8 +14,10 @@
 
 #include "starboard/loader_app/slot_management.h"
 
+#include <stdio.h>
 #include <sys/stat.h>
 
+#include <iostream>
 #include <string>
 #include <vector>
 
@@ -31,6 +33,9 @@
 #include "starboard/loader_app/installation_manager.h"
 #include "starboard/loader_app/installation_store.pb.h"
 #include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/jsoncpp/source/include/json/reader.h"
+#include "third_party/jsoncpp/source/include/json/value.h"
+#include "third_party/jsoncpp/source/include/json/writer.h"
 
 #if SB_IS(EVERGREEN_COMPATIBLE)
 
@@ -40,6 +45,22 @@ namespace {
 
 const char kTestAppKey[] = "1234";
 const char kTestApp2Key[] = "ABCD";
+const char kTestEvergreenVersion1[] = "1.2";
+const char kTestEvergreenVersion2[] = "1.2.1";
+const char kTestEvergreenVersion3[] = "1.2.3";
+const char kTestEvergreenVersion4[] = "2.2.3";
+const kTestSlotIndex = 0;
+// The max length of Evergreen version string.
+const int kMaxEgVersionLength = 20;
+
+// Filename for the manifest file which contains the Evergreen version.
+const char kManifestFileName[] = "manifest.json";
+
+// Deliminator of the Evergreen version string segments.
+const char kEgVersionDeliminator = '.';
+
+// Evergreen version key in the manifest file.
+const char kVersionKey[] = "version";
 
 void SbEventFake(const SbEvent*) {}
 
@@ -468,6 +489,81 @@ TEST_P(SlotManagementTest, BadSabi) {
   SbFileDeleteRecursive(good_path.c_str(), false);
 }
 
+TEST_P(SlotManagementTest, CompareEvergreenVersion) {
+  if (!storage_path_implemented_) {
+    return;
+  }
+  std::vector<char> v1(kTestEvergreenVersion1,
+                       kTestEvergreenVersion1 + strlen(kTestEvergreenVersion1));
+  std::vector<char> v2(kTestEvergreenVersion2,
+                       kTestEvergreenVersion2 + strlen(kTestEvergreenVersion2));
+  std::vector<char> v3(kMaxEgVersionLength);
+  ASSERT_EQ(0, CompareEvergreenVersion(&v1, &v3));
+  ASSERT_EQ(0, CompareEvergreenVersion(&v1, &v1));
+  ASSERT_EQ(-1, CompareEvergreenVersion(&v1, &v2));
+  v3.assign(kTestEvergreenVersion3,
+            kTestEvergreenVersion3 + strlen(kTestEvergreenVersion3));
+  ASSERT_EQ(1, CompareEvergreenVersion(&v3, &v2))
+  std::vector<char> v4(kTestEvergreenVersion4,
+                       kTestEvergreenVersion4 + strlen(kTestEvergreenVersion4));
+  ASSERT_EQ(1, CompareEvergreenVersion(&v4, &v3));
+}
+
+TEST_P(SlotManagementTest, ReadEvergreenVersion) {
+  if (!storage_path_implemented_) {
+    return;
+  }
+  ImInitialize(3, kTestAppKey);
+  ImReset();
+
+  std::vector<char> current_version(kMaxEgVersionLength);
+  Json::Value root;
+  Json::Value manifest_version;
+  manifest_version["manifest_version"] = 2;
+  root.append(manifest_version);
+  Json::StyledStreamWriter writer;
+
+  std::vector<char> installation_path(kSbFileMaxPath);
+  if (ImGetInstallationPath(kTestSlotIndex, installation_path.data(),
+                            kSbFileMaxPath) == IM_ERROR) {
+    SB_LOG(WARNING) << "Failed to get installation path.";
+    return false;
+  }
+  std::vector<char> test_dir_path(kSbFileMaxPath);
+  snprintf(test_dir_path.data(), kSbFileMaxPath, "%s%s%s",
+           installation_path.data(), kSbFileSepString, "test_dir", );
+  std::vector<char> manifest_file_path(kSbFileMaxPath);
+  snprintf(manifest_file_path.data(), kSbFileMaxPath, "%s%s%s",
+           test_dir_path.data(), kSbFileSepString, kManifestFileName);
+
+  ScopedFile manifest_file(manifest_file_path.data(), O_RDWR | O_CREAT,
+                           S_IRWXU | S_IRWXG);
+  std::stringstream manifest_file_s1();
+  writer.write(manifest_file_s1, root);
+  std::string manifest_file_str1 = manifest_file_s1.str();
+  manifest_file.WriteAll(manifest_file_str1.c_str(),
+                         manifest_file_str1.length());
+
+  ASSERT_FALSE(ReadEvergreenVersion(&manifest_file_path, current_version.data(),
+                                    kMaxEgVersionLength));
+
+  Json::Value evergreen_version;
+  evergreen_version[kVersionKey] = kTestEvergreenVersion2;
+  root.append(evergreen_version);
+  std::stringstream manifest_file_s2();
+  writer.write(manifest_file_s2, root);
+  std::string manifest_file_str2 = manifest_file_s2.str();
+  manifest_file.WriteAll(manifest_file_str2.c_str(),
+                         manifest_file_str2.length());
+
+  ASSERT_TRUE(ReadEvergreenVersion(&manifest_file_path, current_version.data(),
+                                   kMaxEgVersionLength));
+  ASSERT_EQ(kTestEvergreenVersion2, current_version.data());
+
+  ImUninitialize();
+  SbFileDeleteRecursive(test_dir_path.data(), false);
+}
+
 INSTANTIATE_TEST_CASE_P(SlotManagementTests,
                         SlotManagementTest,
                         ::testing::Bool());