blob: 1c01d2b662b21ed955a857eaf1c24f3a2a9e79c6 [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <fcntl.h>
#include <fbl/unique_fd.h>
#include <gtest/gtest.h>
#include "sdk/lib/sys/cpp/component_context.h"
#include "src/lib/files/directory.h"
#include "src/lib/files/file.h"
#include "src/lib/files/path.h"
#include "src/lib/files/scoped_temp_dir.h"
#include "src/lib/fxl/strings/substitute.h"
#include "src/lib/testing/loop_fixture/real_loop_fixture.h"
#include "src/sys/appmgr/component_id_index.h"
#include "src/sys/appmgr/realm.h"
namespace component {
namespace {
constexpr char kIndexFilePath[] = "component_id_index";
const char kEmptyComponentIdIndex[] =
R"({ "appmgr_restrict_isolated_persistent_storage": false, "instances": [] })";
const char kExampleInstanceId[] =
"8c90d44863ff67586cf6961081feba4f760decab8bbbee376a3bfbc77b351280";
class StorageTest : public ::gtest::RealLoopFixture {
protected:
StorageTest() { ZX_ASSERT(tmp_dir_.NewTempDir(&root_storage_dir_)); }
void TearDown() override {
// Ensure all posted tasks have completed before we shutdown the dispatcher. This is because
// the task posted by Namespace::RunShutdownIfNoChildren posts another task to the dispatcher
// which completes the shutdown. This will fail if the task is executed as part of the loop's
// destructor, since no more tasks can be posted to the dispatcher during shutdown. In the case
// this test is dependent on any external tasks, this solution may need to be revisited.
RunLoopUntilIdle();
}
// Creates a root realm with label = componnet::internal::kRootLabel ("app").
std::unique_ptr<Realm> CreateRootRealm(const std::string& root_storage_path,
fbl::unique_fd appmgr_config_dir) {
auto environment_services = sys::ServiceDirectory::CreateFromNamespace();
fuchsia::sys::ServiceListPtr root_realm_services(new fuchsia::sys::ServiceList);
files::CreateDirectoryAt(appmgr_config_dir.get(), "scheme_map");
const char scheme_map[] = R"({
"launchers": {
"package": [ "file", "fuchsia-pkg" ]
}
})";
files::WriteFileAt(appmgr_config_dir.get(), "scheme_map/default", scheme_map,
sizeof(scheme_map));
auto component_id_index =
ComponentIdIndex::CreateFromAppmgrConfigDir(appmgr_config_dir).take_value();
fuchsia::sys::EnvironmentOptions opts;
opts.delete_storage_on_death = false;
auto component_context = sys::ComponentContext::Create();
RealmArgs realm_args = RealmArgs::MakeWithCustomLoader(
nullptr, internal::kRootLabel, files::JoinPath(root_storage_dir_, "data"),
files::JoinPath(root_storage_dir_, "data/cache"), files::JoinPath(root_storage_dir_, "tmp"),
std::move(environment_services), std::move(root_realm_services), std::move(opts),
std::move(appmgr_config_dir), std::move(component_id_index),
component_context->svc()->Connect<fuchsia::sys::Loader>());
return Realm::Create(std::move(realm_args));
}
Realm* CreateChildRealm(
Realm* parent, const std::string& label,
fidl::InterfaceRequest<fuchsia::sys::EnvironmentController> env_ctrl_req) {
fuchsia::sys::EnvironmentOptions opts;
opts.delete_storage_on_death = false;
fuchsia::sys::EnvironmentPtr env;
parent->CreateNestedEnvironment(env.NewRequest(), std::move(env_ctrl_req), "child_realm",
nullptr, std::move(opts));
for (const auto& item : parent->children()) {
if (item.first->label() == label) {
return item.first;
}
}
return nullptr;
}
std::string root_storage_dir() { return root_storage_dir_; }
fbl::unique_fd MakeAppmgrConfigDirWithIndex(std::string json_index) {
fbl::unique_fd ufd(open(tmp_dir_.path().c_str(), O_RDONLY));
ZX_ASSERT(ufd.is_valid());
ZX_ASSERT(files::WriteFileAt(ufd.get(), kIndexFilePath, json_index.data(), json_index.size()));
return ufd;
}
private:
files::ScopedTempDir tmp_dir_;
std::string root_storage_dir_;
};
// Test the storage directory path for a component when it doesn't have a component ID
// index.
TEST_F(StorageTest, DirPathWithoutInstanceId) {
auto root_realm =
CreateRootRealm(root_storage_dir(), MakeAppmgrConfigDirWithIndex(kEmptyComponentIdIndex));
fuchsia::sys::EnvironmentControllerPtr child_env_ctrl;
auto child_realm = CreateChildRealm(root_realm.get(), "child_realm", child_env_ctrl.NewRequest());
FuchsiaPkgUrl url;
ZX_ASSERT(url.Parse("fuchsia-pkg://fuchsia.com/my_pkg#meta/my_component.cmx"));
EXPECT_EQ(
child_realm->InitIsolatedPathForComponentInstance(url, internal::StorageType::DATA).value(),
files::JoinPath(root_storage_dir(),
"data/r/child_realm/fuchsia.com:my_pkg:0#meta:my_component.cmx"));
// Ensure that the moniker-based directory is created.
EXPECT_TRUE(files::IsDirectory(files::JoinPath(
root_storage_dir(), "data/r/child_realm/fuchsia.com:my_pkg:0#meta:my_component.cmx")));
}
TEST_F(StorageTest, DoNotRestrictIsolatedPersistentStorageByDefault) {
auto root_realm =
CreateRootRealm(root_storage_dir(), MakeAppmgrConfigDirWithIndex(kEmptyComponentIdIndex));
fuchsia::sys::EnvironmentControllerPtr child_env_ctrl;
auto child_realm = CreateChildRealm(root_realm.get(), "child_realm", child_env_ctrl.NewRequest());
FuchsiaPkgUrl url;
ZX_ASSERT(url.Parse("fuchsia-pkg://fuchsia.com/my_pkg#meta/my_component.cmx"));
EXPECT_EQ(
child_realm->InitIsolatedPathForComponentInstance(url, internal::StorageType::DATA).value(),
files::JoinPath(root_storage_dir(),
"data/r/child_realm/fuchsia.com:my_pkg:0#meta:my_component.cmx"));
// Ensure that the moniker-based directory is created.
EXPECT_TRUE(files::IsDirectory(files::JoinPath(
root_storage_dir(), "data/r/child_realm/fuchsia.com:my_pkg:0#meta:my_component.cmx")));
}
TEST_F(StorageTest, RestrictIsolatedPersistentStorage) {
auto root_realm = CreateRootRealm(root_storage_dir(), MakeAppmgrConfigDirWithIndex(R"({
"appmgr_restrict_isolated_persistent_storage": true,
"instances": []
})"));
fuchsia::sys::EnvironmentControllerPtr child_env_ctrl;
auto child_realm = CreateChildRealm(root_realm.get(), "child_realm", child_env_ctrl.NewRequest());
FuchsiaPkgUrl url;
ZX_ASSERT(url.Parse("fuchsia-pkg://fuchsia.com/my_pkg#meta/my_component.cmx"));
EXPECT_EQ(
child_realm->InitIsolatedPathForComponentInstance(url, internal::StorageType::DATA).error(),
ZX_ERR_ACCESS_DENIED);
// Ensure that a moniker-based directory was not created.
EXPECT_FALSE(files::IsDirectory(files::JoinPath(
root_storage_dir(), "data/r/child_realm/fuchsia.com:my_pkg:0#meta:my_component.cmx")));
}
TEST_F(StorageTest, ComponentControllerAccessDenied) {
auto root_realm = CreateRootRealm(root_storage_dir(), MakeAppmgrConfigDirWithIndex(R"({
"appmgr_restrict_isolated_persistent_storage": true,
"instances": []
})"));
fuchsia::sys::ComponentControllerPtr ctrl_ptr;
bool terminated = false;
ctrl_ptr.events().OnTerminated = [&](int64_t, fuchsia::sys::TerminationReason status) {
EXPECT_EQ(fuchsia::sys::TerminationReason::ACCESS_DENIED, status);
terminated = true;
};
fuchsia::sys::LaunchInfo launch_info;
launch_info.url =
"fuchsia-pkg://fuchsia.com/appmgr_unittests#meta/test_component_using_storage.cmx";
root_realm->CreateComponent(std::move(launch_info), ctrl_ptr.NewRequest());
RunLoopUntil([&] { return terminated; });
}
TEST_F(StorageTest, ComponentControllerSuccess) {
auto root_realm = CreateRootRealm(root_storage_dir(), MakeAppmgrConfigDirWithIndex(R"({
"appmgr_restrict_isolated_persistent_storage": true,
"instances": [
{
"instance_id": "23e58c2c08de24e52c014943d77528d24868af6eca39d10d5f27035c65061277",
"appmgr_moniker": {
"url": "fuchsia-pkg://fuchsia.com/appmgr_unittests#meta/test_component_using_storage.cmx",
"realm_path": [
"app"
]
}
}
]
})"));
fuchsia::sys::ComponentControllerPtr ctrl_ptr;
bool terminated = false;
ctrl_ptr.events().OnTerminated = [&](int64_t code, fuchsia::sys::TerminationReason status) {
EXPECT_EQ(0u, code);
EXPECT_EQ(fuchsia::sys::TerminationReason::EXITED, status);
terminated = true;
};
fuchsia::sys::LaunchInfo launch_info;
launch_info.url =
"fuchsia-pkg://fuchsia.com/appmgr_unittests#meta/test_component_using_storage.cmx";
root_realm->CreateComponent(std::move(launch_info), ctrl_ptr.NewRequest());
RunLoopUntil([&] { return terminated; });
}
// Test the storage directory path for a component when it has a component ID.
TEST_F(StorageTest, DirPathWithInstanceId) {
auto root_realm = CreateRootRealm(
root_storage_dir(), MakeAppmgrConfigDirWithIndex(fxl::Substitute(R"(
{
"instances": [
{
"instance_id": "$0",
"appmgr_moniker": {
"realm_path": ["app", "child_realm"],
"url": "fuchsia-pkg://fuchsia.com/my_pkg#meta/my_component.cmx"
}
}
]
}
)",
kExampleInstanceId)));
fuchsia::sys::EnvironmentControllerPtr child_env_ctrl;
auto child_realm = CreateChildRealm(root_realm.get(), "child_realm", child_env_ctrl.NewRequest());
FuchsiaPkgUrl url;
ZX_ASSERT(url.Parse("fuchsia-pkg://fuchsia.com/my_pkg#meta/my_component.cmx"));
auto actual_storage_path =
child_realm->InitIsolatedPathForComponentInstance(url, internal::StorageType::DATA).value();
EXPECT_EQ(actual_storage_path,
files::JoinPath(root_storage_dir(),
fxl::Substitute("data/persistent/$0", kExampleInstanceId)));
// Ensure that the instance ID based directory is created.
EXPECT_TRUE(files::IsDirectory(files::JoinPath(
root_storage_dir(), fxl::Substitute("data/persistent/$0", kExampleInstanceId))));
// Ensure that the moniker based directory does not exist.
EXPECT_FALSE(files::IsDirectory(files::JoinPath(
root_storage_dir(), "data/r/child_realm/fuchsia.com:my_pkg:0#meta:my_component.cmx")));
}
// Test that when a component's storage directory is moved once it is assigned an instance ID.
TEST_F(StorageTest, MoveDirToInstanceId) {
// Step 1: ensure storage directory exists for component without an instance ID.
{
auto root_realm =
CreateRootRealm(root_storage_dir(), MakeAppmgrConfigDirWithIndex(kEmptyComponentIdIndex));
fuchsia::sys::EnvironmentControllerPtr child_env_ctrl;
auto child_realm =
CreateChildRealm(root_realm.get(), "child_realm", child_env_ctrl.NewRequest());
FuchsiaPkgUrl url;
ZX_ASSERT(url.Parse("fuchsia-pkg://fuchsia.com/my_pkg#meta/my_component.cmx"));
EXPECT_EQ(
child_realm->InitIsolatedPathForComponentInstance(url, internal::StorageType::DATA).value(),
files::JoinPath(root_storage_dir(),
"data/r/child_realm/fuchsia.com:my_pkg:0#meta:my_component.cmx"));
}
// Ensure that the moniker based directory is created.
EXPECT_TRUE(files::IsDirectory(files::JoinPath(
root_storage_dir(), "data/r/child_realm/fuchsia.com:my_pkg:0#meta:my_component.cmx")));
// Step 2: Spin up the root realm again, this time assigning the component with an instance ID.
{
auto root_realm = CreateRootRealm(
root_storage_dir(), MakeAppmgrConfigDirWithIndex(fxl::Substitute(R"(
{
"instances": [
{
"instance_id": "$0",
"appmgr_moniker": {
"realm_path": ["app", "child_realm"],
"url": "fuchsia-pkg://fuchsia.com/my_pkg#meta/my_component.cmx"
}
}
]
}
)",
kExampleInstanceId)));
fuchsia::sys::EnvironmentControllerPtr child_env_ctrl;
auto child_realm =
CreateChildRealm(root_realm.get(), "child_realm", child_env_ctrl.NewRequest());
FuchsiaPkgUrl url;
ZX_ASSERT(url.Parse("fuchsia-pkg://fuchsia.com/my_pkg#meta/my_component.cmx"));
auto actual_storage_path =
child_realm->InitIsolatedPathForComponentInstance(url, internal::StorageType::DATA).value();
EXPECT_EQ(actual_storage_path,
files::JoinPath(root_storage_dir(),
fxl::Substitute("data/persistent/$0", kExampleInstanceId)));
}
// Ensure that the moniker based directory has been moved to the instance ID based directory.
EXPECT_FALSE(files::IsDirectory(files::JoinPath(
root_storage_dir(), "data/r/child_realm/fuchsia.com:my_pkg:0#meta:my_component.cmx")));
EXPECT_TRUE(files::IsDirectory(files::JoinPath(
root_storage_dir(), fxl::Substitute("data/persistent/$0", kExampleInstanceId))));
}
} // namespace
} // namespace component