blob: 25c993db78db14c31e5a43342480983810fe90e3 [file] [log] [blame]
// Copyright 2020 The Cobalt Authors. All Rights Reserved.
//
// 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 "starboard/loader_app/slot_management.h"
#include <sys/stat.h>
#include <string>
#include <vector>
#include "gmock/gmock.h"
#include "starboard/common/file.h"
#include "starboard/configuration_constants.h"
#include "starboard/directory.h"
#include "starboard/elf_loader/sabi_string.h"
#include "starboard/event.h"
#include "starboard/file.h"
#include "starboard/loader_app/app_key_files.h"
#include "starboard/loader_app/drain_file.h"
#include "starboard/loader_app/installation_manager.h"
#include "starboard/loader_app/installation_store.pb.h"
#include "testing/gtest/include/gtest/gtest.h"
#if SB_IS(EVERGREEN_COMPATIBLE)
namespace starboard {
namespace loader_app {
namespace {
const char kTestAppKey[] = "1234";
const char kTestApp2Key[] = "ABCD";
void SbEventFake(const SbEvent*) {}
const char* GetEvergreenSabiStringFake() {
return "bad";
}
const char* GetCobaltUserAgentStringFake() {
return "";
}
class MockLibraryLoader : public LibraryLoader {
public:
MOCK_METHOD4(Load,
bool(const std::string& library_path,
const std::string& content_path,
bool use_compression,
bool use_memory_mapped_file));
MOCK_METHOD1(Resolve, void*(const std::string& symbol));
};
class SlotManagementTest : public testing::TestWithParam<bool> {
protected:
virtual void SetUp() {
slot_0_libcobalt_path_ =
CreatePath({"content", "app", "cobalt", "lib", "libcobalt"});
slot_0_content_path_ = CreatePath({"content", "app", "cobalt", "content"});
slot_1_libcobalt_path_ = CreatePath({"installation_1", "lib", "libcobalt"});
slot_1_content_path_ = CreatePath({"installation_1", "content"});
slot_2_libcobalt_path_ = CreatePath({"installation_2", "lib", "libcobalt"});
slot_2_content_path_ = CreatePath({"installation_2", "content"});
std::vector<char> buf(kSbFileMaxPath);
storage_path_implemented_ = SbSystemGetPath(kSbSystemPathStorageDirectory,
buf.data(), kSbFileMaxPath);
}
void AddFileExtension(std::string& path) {
if (GetParam()) {
path += ".lz4";
} else {
path += ".so";
}
}
std::string CreatePath(std::initializer_list<std::string> path_elements) {
std::string result;
for (const std::string& path : path_elements) {
result += kSbFileSepString;
result += path;
}
return result;
}
bool FileExists(const char* path) {
struct stat info;
return stat(path, &info) == 0;
}
void CreateBadFile(int index, const std::string& app_key) {
std::vector<char> installation_path(kSbFileMaxPath);
ASSERT_EQ(IM_SUCCESS, ImGetInstallationPath(index, installation_path.data(),
kSbFileMaxPath));
std::string bad_app_key_file_path =
starboard::loader_app::GetBadAppKeyFilePath(installation_path.data(),
app_key);
ASSERT_TRUE(!bad_app_key_file_path.empty());
ASSERT_TRUE(starboard::loader_app::CreateAppKeyFile(bad_app_key_file_path));
}
void CreateGoodFile(int index, const std::string& app_key) {
std::vector<char> installation_path(kSbFileMaxPath);
ASSERT_EQ(IM_SUCCESS, ImGetInstallationPath(index, installation_path.data(),
kSbFileMaxPath));
std::string good_app_key_file_path =
starboard::loader_app::GetGoodAppKeyFilePath(installation_path.data(),
app_key);
ASSERT_TRUE(!good_app_key_file_path.empty());
ASSERT_TRUE(
starboard::loader_app::CreateAppKeyFile(good_app_key_file_path));
}
void VerifyGoodFile(int index, const std::string& app_key, bool exists) {
std::vector<char> installation_path(kSbFileMaxPath);
ImGetInstallationPath(index, installation_path.data(), kSbFileMaxPath);
std::string good_app_key_file_path =
starboard::loader_app::GetGoodAppKeyFilePath(installation_path.data(),
app_key);
ASSERT_TRUE(!good_app_key_file_path.empty());
ASSERT_EQ(exists, FileExists(good_app_key_file_path.c_str()));
}
void VerifyBadFile(int index, const std::string& app_key, bool exists) {
std::vector<char> installation_path(kSbFileMaxPath);
ImGetInstallationPath(index, installation_path.data(), kSbFileMaxPath);
std::string bad_app_key_file_path =
starboard::loader_app::GetBadAppKeyFilePath(installation_path.data(),
app_key);
ASSERT_TRUE(!bad_app_key_file_path.empty());
SB_LOG(INFO) << "bad_app_key_file_path=" << bad_app_key_file_path;
ASSERT_EQ(exists, FileExists(bad_app_key_file_path.c_str()));
}
void CreateDrainFile(int index, const std::string& app_key) {
std::vector<char> installation_path(kSbFileMaxPath);
ASSERT_EQ(IM_SUCCESS, ImGetInstallationPath(index, installation_path.data(),
kSbFileMaxPath));
ASSERT_TRUE(DrainFileTryDrain(installation_path.data(), app_key.c_str()));
}
void VerifyLoad(const std::string& lib, const std::string& content) {
bool use_compression = GetParam();
MockLibraryLoader library_loader;
std::string full_lib_path = lib;
AddFileExtension(full_lib_path);
EXPECT_CALL(library_loader,
Load(testing::EndsWith(full_lib_path),
testing::EndsWith(content), use_compression, false))
.Times(1)
.WillOnce(testing::Return(true));
EXPECT_CALL(library_loader, Resolve("GetEvergreenSabiString"))
.Times(1)
.WillOnce(
testing::Return(reinterpret_cast<void*>(&GetEvergreenSabiString)));
EXPECT_CALL(library_loader, Resolve("GetCobaltUserAgentString"))
.Times(1)
.WillOnce(testing::Return(
reinterpret_cast<void*>(&GetCobaltUserAgentStringFake)));
EXPECT_CALL(library_loader, Resolve("SbEventHandle"))
.Times(1)
.WillOnce(testing::Return(reinterpret_cast<void*>(&SbEventFake)));
ASSERT_EQ(&SbEventFake,
LoadSlotManagedLibrary(kTestAppKey, "", &library_loader, false));
}
std::string CreateDirs(const std::string& base,
std::initializer_list<std::string> dirs,
std::string& out_top_created_dir) {
std::string path = base;
for (const std::string& dir : dirs) {
path += kSbFileSepString;
path += dir;
if (!FileExists(path.c_str())) {
struct stat info;
EXPECT_TRUE(mkdir(path.c_str(), 0700) == 0 ||
(stat(path.c_str(), &info) == 0 && S_ISDIR(info.st_mode)));
if (out_top_created_dir.empty()) {
// This new dir should be recursively deleted during cleanup.
out_top_created_dir = path;
}
}
}
return path;
}
std::string CreateEmptyLibraryFile(const std::string& library_path) {
std::string path;
std::string top_created_dir;
if (library_path == slot_0_libcobalt_path_) {
// It's the system slot.
std::vector<char> buf(kSbFileMaxPath);
SbSystemGetPath(kSbSystemPathContentDirectory, buf.data(),
kSbFileMaxPath);
path = CreateDirs(buf.data(), {"app", "cobalt", "lib"}, top_created_dir);
} else {
// It's an installation slot.
std::vector<char> buf(kSbFileMaxPath);
SbSystemGetPath(kSbSystemPathStorageDirectory, buf.data(),
kSbFileMaxPath);
path =
CreateDirs(buf.data(),
{library_path == slot_1_libcobalt_path_ ? "installation_1"
: "installation_2",
"lib"},
top_created_dir);
}
path += kSbFileSepString;
path += "libcobalt";
AddFileExtension(path);
SbFile sb_file = SbFileOpen(path.c_str(), kSbFileOpenAlways | kSbFileRead,
nullptr, nullptr);
EXPECT_TRUE(SbFileIsValid(sb_file));
SbFileClose(sb_file);
return !top_created_dir.empty() ? top_created_dir : path;
}
protected:
std::string slot_0_libcobalt_path_;
std::string slot_0_content_path_;
std::string slot_1_libcobalt_path_;
std::string slot_1_content_path_;
std::string slot_2_libcobalt_path_;
std::string slot_2_content_path_;
bool storage_path_implemented_;
};
TEST_P(SlotManagementTest, SystemSlot) {
if (!storage_path_implemented_) {
return;
}
ImInitialize(3, kTestAppKey);
ImReset();
ImUninitialize();
std::string path = CreateEmptyLibraryFile(slot_0_libcobalt_path_);
VerifyLoad(slot_0_libcobalt_path_, slot_0_content_path_);
VerifyGoodFile(0, kTestAppKey, false);
VerifyBadFile(0, kTestAppKey, false);
SbFileDeleteRecursive(path.c_str(), false);
}
TEST_P(SlotManagementTest, AdoptSlot) {
if (!storage_path_implemented_) {
return;
}
ImInitialize(3, kTestAppKey);
ImReset();
ASSERT_EQ(IM_SUCCESS, ImRollForward(1));
ASSERT_EQ(IM_SUCCESS, ImMarkInstallationSuccessful(1));
ASSERT_EQ(1, ImGetCurrentInstallationIndex());
VerifyGoodFile(1, kTestAppKey, false);
CreateGoodFile(1, kTestApp2Key);
ImUninitialize();
std::string path = CreateEmptyLibraryFile(slot_1_libcobalt_path_);
VerifyLoad(slot_1_libcobalt_path_, slot_1_content_path_);
VerifyGoodFile(1, kTestAppKey, true);
VerifyBadFile(1, kTestAppKey, false);
SbFileDeleteRecursive(path.c_str(), false);
}
TEST_P(SlotManagementTest, GoodSlot) {
if (!storage_path_implemented_) {
return;
}
ImInitialize(3, kTestAppKey);
ImReset();
ASSERT_EQ(IM_SUCCESS, ImRollForward(2));
ASSERT_EQ(IM_SUCCESS, ImMarkInstallationSuccessful(2));
ASSERT_EQ(2, ImGetCurrentInstallationIndex());
CreateGoodFile(2, kTestAppKey);
ImUninitialize();
std::string path = CreateEmptyLibraryFile(slot_2_libcobalt_path_);
VerifyLoad(slot_2_libcobalt_path_, slot_2_content_path_);
VerifyGoodFile(2, kTestAppKey, true);
VerifyBadFile(2, kTestAppKey, false);
SbFileDeleteRecursive(path.c_str(), false);
}
TEST_P(SlotManagementTest, NotAdoptSlot) {
if (!storage_path_implemented_) {
return;
}
ImInitialize(3, kTestAppKey);
ImReset();
ASSERT_EQ(IM_SUCCESS, ImRollForward(2));
ASSERT_EQ(IM_SUCCESS, ImMarkInstallationSuccessful(2));
ASSERT_EQ(2, ImGetCurrentInstallationIndex());
VerifyGoodFile(2, kTestAppKey, false);
ImUninitialize();
std::string path = CreateEmptyLibraryFile(slot_0_libcobalt_path_);
VerifyLoad(slot_0_libcobalt_path_, slot_0_content_path_);
VerifyGoodFile(2, kTestAppKey, false);
VerifyBadFile(2, kTestAppKey, true);
SbFileDeleteRecursive(path.c_str(), false);
}
TEST_P(SlotManagementTest, BadSlot) {
if (!storage_path_implemented_) {
return;
}
ImInitialize(3, kTestAppKey);
ImReset();
ASSERT_EQ(IM_SUCCESS, ImRollForward(1));
ASSERT_EQ(IM_SUCCESS, ImMarkInstallationSuccessful(1));
ASSERT_EQ(1, ImGetCurrentInstallationIndex());
CreateBadFile(1, kTestAppKey);
ImUninitialize();
std::string path = CreateEmptyLibraryFile(slot_0_libcobalt_path_);
VerifyLoad(slot_0_libcobalt_path_, slot_0_content_path_);
VerifyGoodFile(1, kTestAppKey, false);
SbFileDeleteRecursive(path.c_str(), false);
}
TEST_P(SlotManagementTest, DrainingSlot) {
if (!storage_path_implemented_) {
return;
}
ImInitialize(3, kTestAppKey);
ImReset();
ASSERT_EQ(IM_SUCCESS, ImRollForward(1));
ASSERT_EQ(IM_SUCCESS, ImMarkInstallationSuccessful(1));
ASSERT_EQ(1, ImGetCurrentInstallationIndex());
CreateDrainFile(1, kTestApp2Key);
ImUninitialize();
std::string path = CreateEmptyLibraryFile(slot_0_libcobalt_path_);
VerifyLoad(slot_0_libcobalt_path_, slot_0_content_path_);
VerifyGoodFile(1, kTestAppKey, false);
VerifyBadFile(1, kTestAppKey, false);
SbFileDeleteRecursive(path.c_str(), false);
}
TEST_P(SlotManagementTest, AlternativeContent) {
if (!storage_path_implemented_) {
return;
}
ImInitialize(3, kTestAppKey);
ImReset();
ASSERT_EQ(IM_SUCCESS, ImRollForward(1));
ASSERT_EQ(IM_SUCCESS, ImMarkInstallationSuccessful(1));
ASSERT_EQ(1, ImGetCurrentInstallationIndex());
ImUninitialize();
std::string path = CreateEmptyLibraryFile(slot_0_libcobalt_path_);
MockLibraryLoader library_loader;
std::string full_lib_path = slot_0_libcobalt_path_;
AddFileExtension(full_lib_path);
EXPECT_CALL(library_loader,
Load(testing::EndsWith(full_lib_path), testing::EndsWith("/foo"),
GetParam(), false))
.Times(1)
.WillOnce(testing::Return(true));
EXPECT_CALL(library_loader, Resolve("GetEvergreenSabiString"))
.Times(1)
.WillOnce(
testing::Return(reinterpret_cast<void*>(&GetEvergreenSabiString)));
EXPECT_CALL(library_loader, Resolve("GetCobaltUserAgentString"))
.Times(1)
.WillOnce(testing::Return(
reinterpret_cast<void*>(&GetCobaltUserAgentStringFake)));
EXPECT_CALL(library_loader, Resolve("SbEventHandle"))
.Times(1)
.WillOnce(testing::Return(reinterpret_cast<void*>(&SbEventFake)));
ASSERT_EQ(&SbEventFake, LoadSlotManagedLibrary(kTestAppKey, "/foo",
&library_loader, false));
SbFileDeleteRecursive(path.c_str(), false);
}
TEST_P(SlotManagementTest, BadSabi) {
if (!storage_path_implemented_) {
return;
}
ImInitialize(3, kTestAppKey);
ImReset();
ASSERT_EQ(IM_SUCCESS, ImRollForward(1));
ASSERT_EQ(IM_SUCCESS, ImMarkInstallationSuccessful(1));
ASSERT_EQ(1, ImGetCurrentInstallationIndex());
CreateGoodFile(1, kTestAppKey);
ASSERT_EQ(IM_SUCCESS, ImRollForward(2));
ASSERT_EQ(IM_SUCCESS, ImMarkInstallationSuccessful(2));
ASSERT_EQ(2, ImGetCurrentInstallationIndex());
CreateGoodFile(2, kTestAppKey);
ImUninitialize();
std::string bad_path = CreateEmptyLibraryFile(slot_2_libcobalt_path_);
std::string good_path = CreateEmptyLibraryFile(slot_1_libcobalt_path_);
MockLibraryLoader library_loader;
std::string slot2_libcobalt_full = slot_2_libcobalt_path_;
AddFileExtension(slot2_libcobalt_full);
EXPECT_CALL(library_loader,
Load(testing::EndsWith(slot2_libcobalt_full),
testing::EndsWith(slot_2_content_path_), GetParam(), false))
.Times(1)
.WillOnce(testing::Return(true));
EXPECT_CALL(library_loader, Resolve("GetEvergreenSabiString"))
.Times(2)
.WillOnce(
testing::Return(reinterpret_cast<void*>(&GetEvergreenSabiStringFake)))
.WillOnce(
testing::Return(reinterpret_cast<void*>(&GetEvergreenSabiString)));
std::string slot1_libcobalt_full = slot_1_libcobalt_path_;
AddFileExtension(slot1_libcobalt_full);
EXPECT_CALL(library_loader,
Load(testing::EndsWith(slot1_libcobalt_full),
testing::EndsWith(slot_1_content_path_), GetParam(), false))
.Times(1)
.WillOnce(testing::Return(true));
EXPECT_CALL(library_loader, Resolve("GetCobaltUserAgentString"))
.Times(1)
.WillOnce(testing::Return(
reinterpret_cast<void*>(&GetCobaltUserAgentStringFake)));
EXPECT_CALL(library_loader, Resolve("SbEventHandle"))
.Times(1)
.WillOnce(testing::Return(reinterpret_cast<void*>(&SbEventFake)));
ASSERT_EQ(&SbEventFake,
LoadSlotManagedLibrary(kTestAppKey, "", &library_loader, false));
SbFileDeleteRecursive(bad_path.c_str(), false);
SbFileDeleteRecursive(good_path.c_str(), false);
}
INSTANTIATE_TEST_CASE_P(SlotManagementTests,
SlotManagementTest,
::testing::Bool());
} // namespace
} // namespace loader_app
} // namespace starboard
#endif // #if SB_IS(EVERGREEN_COMPATIBLE)