diff --git a/starboard/loader_app/BUILD.gn b/starboard/loader_app/BUILD.gn index 5bf78822e06f..7e0c087ff9b9 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 #include +#include +#include +#include +#include #include +#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* v1, std::vector* v2) { + if ((*v1)[0] == '\0' || (*v2)[0] == '\0') { + return 0; + } + + // Split the version strings into segments of numbers + std::vector 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* 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 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 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 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 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 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, ¤t_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 #include +#include #include #include @@ -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 v1(kTestEvergreenVersion1, + kTestEvergreenVersion1 + strlen(kTestEvergreenVersion1)); + std::vector v2(kTestEvergreenVersion2, + kTestEvergreenVersion2 + strlen(kTestEvergreenVersion2)); + std::vector 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 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 current_version(kMaxEgVersionLength); + Json::Value root; + Json::Value manifest_version; + manifest_version["manifest_version"] = 2; + root.append(manifest_version); + Json::StyledStreamWriter writer; + + std::vector installation_path(kSbFileMaxPath); + if (ImGetInstallationPath(kTestSlotIndex, installation_path.data(), + kSbFileMaxPath) == IM_ERROR) { + SB_LOG(WARNING) << "Failed to get installation path."; + return false; + } + std::vector test_dir_path(kSbFileMaxPath); + snprintf(test_dir_path.data(), kSbFileMaxPath, "%s%s%s", + installation_path.data(), kSbFileSepString, "test_dir", ); + std::vector 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());