| // 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 <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_)); } |
| |
| // 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), false, 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 |