| // Copyright 2016 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 "src/sys/appmgr/realm.h" |
| |
| #include <fcntl.h> |
| #include <fuchsia/io/llcpp/fidl.h> |
| #include <lib/async/default.h> |
| #include <lib/fdio/directory.h> |
| #include <lib/fdio/fd.h> |
| #include <lib/fdio/fdio.h> |
| #include <lib/fdio/namespace.h> |
| #include <lib/fdio/spawn.h> |
| #include <lib/sys/cpp/service_directory.h> |
| #include <lib/syslog/cpp/macros.h> |
| #include <lib/trace/event.h> |
| #include <lib/zx/process.h> |
| #include <lib/zx/status.h> |
| #include <unistd.h> |
| #include <zircon/errors.h> |
| #include <zircon/process.h> |
| #include <zircon/processargs.h> |
| #include <zircon/rights.h> |
| #include <zircon/status.h> |
| |
| #include <algorithm> |
| #include <memory> |
| #include <utility> |
| |
| #include "src/lib/cmx/cmx.h" |
| #include "src/lib/cmx/program.h" |
| #include "src/lib/cmx/runtime.h" |
| #include "src/lib/cmx/sandbox.h" |
| #include "src/lib/files/directory.h" |
| #include "src/lib/files/file.h" |
| #include "src/lib/files/path.h" |
| #include "src/lib/fsl/handles/object_info.h" |
| #include "src/lib/fsl/io/fd.h" |
| #include "src/lib/fsl/vmo/file.h" |
| #include "src/lib/fsl/vmo/strings.h" |
| #include "src/lib/fxl/memory/weak_ptr.h" |
| #include "src/lib/fxl/strings/concatenate.h" |
| #include "src/lib/fxl/strings/string_printf.h" |
| #include "src/lib/fxl/strings/substitute.h" |
| #include "src/lib/json_parser/json_parser.h" |
| #include "src/lib/pkg_url/url_resolver.h" |
| #include "src/sys/appmgr/constants.h" |
| #include "src/sys/appmgr/crash_introspector.h" |
| #include "src/sys/appmgr/dynamic_library_loader.h" |
| #include "src/sys/appmgr/hub/realm_hub.h" |
| #include "src/sys/appmgr/moniker.h" |
| #include "src/sys/appmgr/namespace.h" |
| #include "src/sys/appmgr/namespace_builder.h" |
| #include "src/sys/appmgr/policy_checker.h" |
| #include "src/sys/appmgr/scheme_map.h" |
| #include "src/sys/appmgr/util.h" |
| |
| namespace component { |
| |
| namespace { |
| |
| constexpr char kAppPath[] = "bin/app"; |
| constexpr char kDataPathPrefix[] = "data/"; |
| constexpr char kDataKey[] = "data"; |
| constexpr char kBinaryKey[] = "binary"; |
| constexpr char kAppArgv0Prefix[] = "/pkg/"; |
| constexpr zx_status_t kComponentCreationFailed = -1; |
| |
| using fuchsia::sys::TerminationReason; |
| |
| void PushHandle(uint32_t id, zx_handle_t handle, std::vector<fdio_spawn_action_t>* actions) { |
| actions->push_back({.action = FDIO_SPAWN_ACTION_ADD_HANDLE, .h = {.id = id, .handle = handle}}); |
| // Currently clang static analyzer could not analyze union correctly thus it does not know handle |
| // is associated with actions. |
| // #TODO(fxbug.dev/64385): Remove this hack once we improve static analyzer. |
| #ifdef __clang_analyzer__ |
| (*actions)[actions->size() - 1].h.handle = handle; |
| #endif // #ifdef __clang_analyzer__ |
| } |
| |
| void PushFileDescriptor(fuchsia::sys::FileDescriptorPtr fd, int target_fd, |
| std::vector<fdio_spawn_action_t>* actions) { |
| if (!fd) { |
| actions->push_back({.action = FDIO_SPAWN_ACTION_CLONE_FD, |
| .fd = {.local_fd = target_fd, .target_fd = target_fd}}); |
| return; |
| } |
| if (fd->type0) { |
| uint32_t id = PA_HND(PA_HND_TYPE(fd->type0), target_fd); |
| PushHandle(id, fd->handle0.release(), actions); |
| } |
| if (fd->type1) { |
| uint32_t id = PA_HND(PA_HND_TYPE(fd->type1), target_fd); |
| PushHandle(id, fd->handle1.release(), actions); |
| } |
| if (fd->type2) { |
| uint32_t id = PA_HND(PA_HND_TYPE(fd->type2), target_fd); |
| PushHandle(id, fd->handle2.release(), actions); |
| } |
| } |
| |
| zx::process CreateProcess(const zx::job& job, zx::vmo executable, const std::string& argv0, |
| const std::vector<std::string>& env_vars, |
| fuchsia::sys::LaunchInfo launch_info, zx::channel loader_service, |
| fdio_flat_namespace_t* flat) { |
| TRACE_DURATION("appmgr", "Realm::CreateProcess", "launch_info.url", launch_info.url); |
| if (!executable) |
| return zx::process(); |
| |
| zx::job duplicate_job; |
| zx_status_t status = job.duplicate(ZX_RIGHT_SAME_RIGHTS, &duplicate_job); |
| if (status != ZX_OK) |
| return zx::process(); |
| |
| std::string label = Util::GetLabelFromURL(launch_info.url); |
| std::vector<const char*> argv{argv0.c_str()}; |
| if (launch_info.arguments.has_value()) { |
| argv.reserve(launch_info.arguments->size() + 2); |
| for (const auto& arg : *launch_info.arguments) { |
| argv.push_back(arg.c_str()); |
| } |
| } |
| argv.push_back(nullptr); |
| |
| std::vector<const char*> environ; |
| environ.reserve(env_vars.size() + 1); |
| for (const auto& env_var : env_vars) { |
| environ.push_back(env_var.c_str()); |
| } |
| environ.push_back(nullptr); |
| |
| uint32_t flags = FDIO_SPAWN_CLONE_UTC_CLOCK; |
| |
| std::vector<fdio_spawn_action_t> actions; |
| |
| PushHandle(PA_JOB_DEFAULT, duplicate_job.release(), &actions); |
| |
| if (loader_service) { |
| PushHandle(PA_LDSVC_LOADER, loader_service.release(), &actions); |
| } else { |
| // TODO(fxbug.dev/3960): Processes that don't have their own package use the appmgr's |
| // dynamic library loader, which doesn't make much sense. We need to find an |
| // appropriate loader service for each executable. |
| flags |= FDIO_SPAWN_DEFAULT_LDSVC; |
| } |
| |
| zx::channel directory_request = std::move(launch_info.directory_request); |
| if (directory_request) { |
| PushHandle(PA_DIRECTORY_REQUEST, directory_request.release(), &actions); |
| } |
| |
| // TODO(fxbug.dev/49824): Appmgr used to receive a fully-privileged debuglog handle as |
| // its stdin, which it would copy to give as a stdin to spawned processes. |
| // This handle is mostly useless as a stdin handle, except for the fact that |
| // some v1 components assert on being able to clone stdin when creating new |
| // processes. Appmgr no longer receives a stdin (or stdout) handle as of |
| // fxrev.dev/370683, so as to not break v1 components that assume a valid |
| // stdin we clone appmgr's stdin handle which is a closed socket set at |
| // startup. |
| // |
| // Appmgr's stdout handle is populated on startup using |
| // StdoutToDebuglog::Init, which installs a write-only debuglog as stdout and |
| // stderr, so cloning this handle for new processes gives the same handle |
| // (albeit without read rights) as appmgr used to hand out. |
| actions.push_back({.action = FDIO_SPAWN_ACTION_CLONE_FD, |
| .fd = {.local_fd = STDIN_FILENO, .target_fd = STDIN_FILENO}}); |
| PushFileDescriptor(std::move(launch_info.out), STDOUT_FILENO, &actions); |
| PushFileDescriptor(std::move(launch_info.err), STDERR_FILENO, &actions); |
| |
| actions.push_back({.action = FDIO_SPAWN_ACTION_SET_NAME, .name = {.data = label.c_str()}}); |
| |
| for (size_t i = 0; i < flat->count; ++i) { |
| actions.push_back({.action = FDIO_SPAWN_ACTION_ADD_NS_ENTRY, |
| .ns = {.prefix = flat->path[i], .handle = flat->handle[i]}}); |
| } |
| |
| executable.set_property(ZX_PROP_NAME, label.data(), label.size()); |
| |
| zx::process process; |
| char err_msg[FDIO_SPAWN_ERR_MSG_MAX_LENGTH]; |
| status = fdio_spawn_vmo(job.get(), flags, executable.release(), argv.data(), environ.data(), |
| actions.size(), actions.data(), process.reset_and_get_address(), err_msg); |
| |
| if (status != ZX_OK) { |
| FX_LOGS(ERROR) << "Cannot run executable " << label << " due to error " << status << " (" |
| << zx_status_get_string(status) << "): " << err_msg; |
| } |
| |
| return process; |
| } |
| |
| bool IsValidEnvironmentLabel(const std::string& label) { |
| static const std::regex* const kEnvironmentLabelRegex = new std::regex{"[0-9a-zA-Z\\.\\-_:#]+"}; |
| |
| // The regex technically covers the empty check, but checking separately |
| // allows us to print a more useful error message. |
| if (label.empty()) { |
| FX_LOGS(ERROR) << "Environment label cannot be empty"; |
| return false; |
| } |
| if (!std::regex_match(label, *kEnvironmentLabelRegex)) { |
| FX_LOGS(ERROR) << "Environment label '" << label << "' contains invalid characters"; |
| return false; |
| } |
| if (label == "." || label == "..") { |
| FX_LOGS(ERROR) << "Environment label cannot be '.' or '..'"; |
| return false; |
| } |
| return true; |
| } |
| |
| // Returns a unique ID for the component containing all of the 'stable' pieces |
| // of the component URL, i.e. the repo/host name, package name, variant, and |
| // resource path but not the package hash/version. This ID is used as a |
| // filesystem path component. |
| std::string ComponentUrlToPathComponent(const FuchsiaPkgUrl& fp) { |
| // If the parsed URL did not include a resource path, the default is used. |
| // TODO(fxbug.dev/4053): Remove this default once all component URLs include a |
| // resource path. |
| std::string resource = fp.resource_path(); |
| if (resource.empty()) { |
| resource = fp.GetDefaultComponentCmxPath(); |
| } |
| std::replace(resource.begin(), resource.end(), '/', ':'); |
| return fxl::Substitute("$0:$1:$2#$3", fp.host_name(), fp.package_name(), fp.variant(), resource); |
| } |
| } // namespace |
| |
| // static |
| RealmArgs RealmArgs::Make(fxl::WeakPtr<Realm> parent, std::string label, std::string data_path, |
| std::string cache_path, std::string temp_path, |
| const std::shared_ptr<sys::ServiceDirectory>& env_services, |
| bool run_virtual_console, fuchsia::sys::EnvironmentOptions options, |
| fbl::unique_fd appmgr_config_dir, |
| fbl::RefPtr<ComponentIdIndex> component_id_index) { |
| return {.parent = parent, |
| .label = label, |
| .data_path = data_path, |
| .cache_path = cache_path, |
| .temp_path = temp_path, |
| .environment_services = env_services, |
| .run_virtual_console = run_virtual_console, |
| .additional_services = nullptr, |
| .options = std::move(options), |
| .appmgr_config_dir = std::move(appmgr_config_dir), |
| .component_id_index = std::move(component_id_index), |
| .loader = std::nullopt}; |
| } |
| |
| RealmArgs RealmArgs::MakeWithAdditionalServices( |
| fxl::WeakPtr<Realm> parent, std::string label, std::string data_path, std::string cache_path, |
| std::string temp_path, const std::shared_ptr<sys::ServiceDirectory>& env_services, |
| bool run_virtual_console, fuchsia::sys::ServiceListPtr additional_services, |
| fuchsia::sys::EnvironmentOptions options, fbl::unique_fd appmgr_config_dir, |
| fbl::RefPtr<ComponentIdIndex> component_id_index) { |
| return {.parent = parent, |
| .label = label, |
| .data_path = data_path, |
| .cache_path = cache_path, |
| .temp_path = temp_path, |
| .environment_services = env_services, |
| .run_virtual_console = run_virtual_console, |
| .additional_services = std::move(additional_services), |
| .options = std::move(options), |
| .appmgr_config_dir = std::move(appmgr_config_dir), |
| .component_id_index = std::move(component_id_index), |
| .loader = std::nullopt}; |
| } |
| |
| RealmArgs RealmArgs::MakeWithCustomLoader( |
| fxl::WeakPtr<Realm> parent, std::string label, std::string data_path, std::string cache_path, |
| std::string temp_path, const std::shared_ptr<sys::ServiceDirectory>& env_services, |
| bool run_virtual_console, fuchsia::sys::ServiceListPtr additional_services, |
| fuchsia::sys::EnvironmentOptions options, fbl::unique_fd appmgr_config_dir, |
| fbl::RefPtr<ComponentIdIndex> component_id_index, fuchsia::sys::LoaderPtr loader) { |
| return {.parent = parent, |
| .label = label, |
| .data_path = data_path, |
| .cache_path = cache_path, |
| .temp_path = temp_path, |
| .environment_services = env_services, |
| .run_virtual_console = run_virtual_console, |
| .additional_services = std::move(additional_services), |
| .options = std::move(options), |
| .appmgr_config_dir = std::move(appmgr_config_dir), |
| .component_id_index = std::move(component_id_index), |
| .loader = std::optional<fuchsia::sys::LoaderPtr>{std::move(loader)}}; |
| } |
| |
| std::unique_ptr<Realm> Realm::Create(RealmArgs args) { |
| if (args.label.empty()) { |
| FX_LOGS(ERROR) << "Cannot create realm with empty label"; |
| return nullptr; |
| } |
| |
| // parent_ is null if this is the root application environment. if so, we |
| // derive from the application manager's job. |
| zx::unowned<zx::job> parent_job; |
| if (args.parent) { |
| parent_job = zx::unowned<zx::job>(args.parent->job_); |
| } else { |
| parent_job = zx::unowned<zx::job>(zx::job::default_job()); |
| } |
| |
| zx::job job; |
| auto status = zx::job::create(*parent_job, 0u, &job); |
| if (status != ZX_OK) { |
| FX_LOGS(ERROR) << "Job creation failed (" << zx_status_get_string(status) |
| << "). Cannot create realm '" << args.label << "'"; |
| return nullptr; |
| } |
| |
| return std::make_unique<Realm>(std::move(args), std::move(job)); |
| } |
| |
| Realm* GetRootRealm(Realm* r) { |
| for (; r->parent(); r = r->parent().get()) { |
| } |
| return r; |
| } |
| |
| Realm::Realm(RealmArgs args, zx::job job) |
| : parent_(args.parent), |
| data_path_(args.data_path), |
| cache_path_(args.cache_path), |
| temp_path_(args.temp_path), |
| run_virtual_console_(args.run_virtual_console), |
| job_(std::move(job)), |
| hub_(fbl::AdoptRef(new fs::PseudoDir())), |
| info_vfs_(async_get_default_dispatcher()), |
| environment_services_(args.environment_services), |
| appmgr_config_dir_(std::move(args.appmgr_config_dir)), |
| use_parent_runners_(args.options.use_parent_runners), |
| delete_storage_on_death_(args.options.delete_storage_on_death), |
| cpu_watcher_(args.cpu_watcher), |
| component_id_index_(std::move(args.component_id_index)), |
| weak_ptr_factory_(this) { |
| // Only need to create this channel for the root realm. |
| if (!parent_) { |
| auto status = |
| zx::channel::create(0, &first_nested_realm_svc_server_, &first_nested_realm_svc_client_); |
| FX_CHECK(status == ZX_OK) << "Cannot create channel: " << zx_status_get_string(status); |
| } |
| |
| koid_ = std::to_string(fsl::GetKoid(job_.get())); |
| |
| label_ = args.label.substr(0, fuchsia::sys::kLabelMaxLength); |
| |
| if (parent_) { |
| log_connector_ = parent_->log_connector_->NewChild(label_); |
| } else { |
| log_connector_ = AdoptRef(new LogConnectorImpl(label_)); |
| } |
| |
| if (args.options.kill_on_oom) { |
| size_t property_value = 1; |
| job_.set_property(ZX_PROP_JOB_KILL_ON_OOM, &property_value, sizeof(property_value)); |
| } |
| |
| if (args.options.inherit_parent_services && parent_->default_namespace_) { |
| default_namespace_ = Namespace::CreateChildNamespace( |
| parent_->default_namespace_, weak_ptr(), std::move(args.additional_services), nullptr); |
| } else { |
| default_namespace_ = |
| fxl::MakeRefCounted<Namespace>(weak_ptr(), std::move(args.additional_services), nullptr); |
| } |
| |
| fsl::SetObjectName(job_.get(), label_); |
| hub_.SetName(label_); |
| hub_.SetJobId(koid_); |
| hub_.AddServices(default_namespace_->services()); |
| hub_.AddJobProvider(fbl::AdoptRef(new fs::Service([this](zx::channel channel) { |
| default_namespace_->job_provider()->AddBinding( |
| fidl::InterfaceRequest<fuchsia::sys::JobProvider>(std::move(channel))); |
| return ZX_OK; |
| }))); |
| |
| // Add default services hosted by appmgr for the root realm only. |
| if (!parent_) { |
| // Set up Loader service for root realm. |
| package_loader_.reset(new component::PackageLoader); |
| default_namespace_->services()->AddService( |
| fuchsia::sys::Loader::Name_, fbl::AdoptRef(new fs::Service([this](zx::channel channel) { |
| package_loader_->AddBinding( |
| fidl::InterfaceRequest<fuchsia::sys::Loader>(std::move(channel))); |
| return ZX_OK; |
| }))); |
| |
| // Set up CacheControl service for root realm. |
| cache_control_.reset(new component::CacheControl); |
| default_namespace_->services()->AddService( |
| fuchsia::sys::test::CacheControl::Name_, |
| fbl::AdoptRef(new fs::Service([this](zx::channel channel) { |
| cache_control_->AddBinding( |
| fidl::InterfaceRequest<fuchsia::sys::test::CacheControl>(std::move(channel))); |
| return ZX_OK; |
| }))); |
| |
| crash_introspector_ = std::make_unique<CrashIntrospector>(); |
| default_namespace_->services()->AddService( |
| fuchsia::sys::internal::CrashIntrospect::Name_, |
| fbl::AdoptRef(new fs::Service([this](zx::channel channel) { |
| crash_introspector_->AddBinding( |
| fidl::InterfaceRequest<fuchsia::sys::internal::CrashIntrospect>(std::move(channel))); |
| return ZX_OK; |
| }))); |
| } |
| |
| if (args.loader) { |
| loader_ = std::move(args.loader.value()); |
| } else { |
| fuchsia::sys::ServiceProviderPtr service_provider; |
| default_namespace_->services()->AddBinding(service_provider.NewRequest()); |
| service_provider->ConnectToService(fuchsia::sys::Loader::Name_, |
| loader_.NewRequest().TakeChannel()); |
| } |
| |
| std::string error; |
| if (!files::IsDirectoryAt(appmgr_config_dir_.get(), SchemeMap::kConfigDirPath)) { |
| FX_LOGS(FATAL) << "Could not find scheme map config dir: " << SchemeMap::kConfigDirPath; |
| } |
| if (!scheme_map_.ParseFromDirectoryAt(appmgr_config_dir_, SchemeMap::kConfigDirPath)) { |
| FX_LOGS(FATAL) << "Could not parse scheme map config dir: " << scheme_map_.error_str(); |
| } |
| } |
| |
| Realm::~Realm() { |
| job_.kill(); |
| |
| ShutdownNamespace(); |
| |
| if (delete_storage_on_death_) { |
| if (!files::DeletePath(data_path(), true)) { |
| FX_LOGS(ERROR) << "Failed to delete persistent storage for environment '" << label() |
| << "' on death"; |
| } |
| if (!files::DeletePath(cache_path(), true)) { |
| FX_LOGS(ERROR) << "Failed to delete cache storage for environment '" << label() |
| << "' on death"; |
| } |
| } |
| } |
| |
| zx::channel Realm::OpenInfoDir() { return Util::OpenAsDirectory(&info_vfs_, hub_dir()); } |
| |
| HubInfo Realm::HubInfo() { return component::HubInfo(label_, koid_, hub_.dir()); } |
| |
| zx::job Realm::DuplicateJobForHub() const { |
| zx::job duplicate_job; |
| // As this only goes inside /hub, it is fine to give destoy rights |
| auto flags = ZX_RIGHTS_BASIC | ZX_RIGHT_DESTROY | ZX_RIGHT_GET_PROPERTY | ZX_RIGHT_ENUMERATE; |
| zx_status_t status = job_.duplicate(flags | ZX_RIGHT_WRITE, &duplicate_job); |
| if (status == ZX_ERR_INVALID_ARGS) { |
| // In the process of removing WRITE for processes; if duplicate with WRITE |
| // failed, try the new rights. TODO(fxbug.dev/32803): Once the transition is |
| // complete, only duplicate with MANAGE_PROCESS. |
| status = job_.duplicate(flags | ZX_RIGHT_MANAGE_PROCESS, &duplicate_job); |
| } |
| if (status != ZX_OK) { |
| return zx::job(); |
| } |
| return duplicate_job; |
| } |
| |
| void Realm::CreateNestedEnvironment( |
| fidl::InterfaceRequest<fuchsia::sys::Environment> environment, |
| fidl::InterfaceRequest<fuchsia::sys::EnvironmentController> controller_request, |
| std::string label, fuchsia::sys::ServiceListPtr additional_services, |
| fuchsia::sys::EnvironmentOptions options) { |
| TRACE_DURATION("appmgr", "Realm::CreateNestedEnvironment", "label", label); |
| |
| // Check that label is valid and unique among existing children. |
| if (!IsValidEnvironmentLabel(label)) { |
| environment.Close(ZX_ERR_INVALID_ARGS); |
| controller_request.Close(ZX_ERR_INVALID_ARGS); |
| return; |
| } |
| for (const auto& child : children_) { |
| if (label == child.first->label_) { |
| FX_LOGS(ERROR) << "Attempt to create nested environment '" << label << "' under '" << label_ |
| << "' but label matches existing environment"; |
| environment.Close(ZX_ERR_BAD_STATE); |
| controller_request.Close(ZX_ERR_BAD_STATE); |
| return; |
| } |
| } |
| |
| if (additional_services && !additional_services->host_directory) { |
| FX_LOGS(ERROR) << label << ": |additional_services.provider| is not supported for " |
| << "CreateNestedEnvironment. Use " |
| << "|additional_services.host_directory| instead."; |
| environment.Close(ZX_ERR_INVALID_ARGS); |
| controller_request.Close(ZX_ERR_INVALID_ARGS); |
| return; |
| } |
| |
| RealmArgs args; |
| std::string nested_data_path = files::JoinPath(data_path(), "r/" + label); |
| std::string nested_cache_path = files::JoinPath(cache_path(), "r/" + label); |
| std::string nested_temp_path = files::JoinPath(temp_path(), "r/" + label); |
| if (additional_services) { |
| args = RealmArgs::MakeWithAdditionalServices( |
| weak_ptr(), label, nested_data_path, nested_cache_path, nested_temp_path, |
| environment_services_, |
| /*run_virtual_console=*/false, std::move(additional_services), std::move(options), |
| appmgr_config_dir_.duplicate(), component_id_index_); |
| } else { |
| args = RealmArgs::Make(weak_ptr(), label, nested_data_path, nested_cache_path, nested_temp_path, |
| environment_services_, |
| /*run_virtual_console=*/false, std::move(options), |
| appmgr_config_dir_.duplicate(), component_id_index_); |
| } |
| args.cpu_watcher = cpu_watcher_; |
| |
| auto realm = Realm::Create(std::move(args)); |
| if (!realm) { |
| return; |
| } |
| |
| auto controller = |
| std::make_unique<EnvironmentControllerImpl>(std::move(controller_request), std::move(realm)); |
| Realm* child = controller->realm(); |
| child->AddBinding(std::move(environment)); |
| |
| // update hub |
| hub_.AddRealm(child->HubInfo()); |
| |
| // If this is the first nested realm created in the root realm, serve the |
| // child realm's service directory on this channel so that |
| // BindFirstNestedRealmSvc can be used to connect to it. |
| if (!parent_ && children_.empty()) { |
| child->default_namespace_->ServeServiceDirectory(std::move(first_nested_realm_svc_server_)); |
| } |
| |
| controller->OnCreated(); |
| children_.emplace(child, std::move(controller)); |
| |
| if (run_virtual_console_) { |
| // TODO(anmittal): remove svc hardcoding once we no longer need to launch |
| // shell with sysmgr services, i.e once we have chrealm. |
| CreateShell("/boot/bin/run-vc", child->default_namespace_->OpenServicesAsDirectory()); |
| CreateShell("/boot/bin/run-vc", child->default_namespace_->OpenServicesAsDirectory()); |
| CreateShell("/boot/bin/run-vc", child->default_namespace_->OpenServicesAsDirectory()); |
| } |
| } |
| |
| void Realm::Resolve(fidl::StringPtr name, fuchsia::process::Resolver::ResolveCallback callback) { |
| TRACE_DURATION("appmgr", "Realm::ResolveLoader", "name", name.value_or("")); |
| |
| zx::vmo binary; |
| fidl::InterfaceHandle<fuchsia::ldsvc::Loader> loader; |
| |
| if (name->empty()) { |
| FX_LOGS(ERROR) << "Cannot resolve loader because requested name is empty"; |
| callback(ZX_ERR_NOT_FOUND, std::move(binary), std::move(loader)); |
| return; |
| } |
| |
| // XXX(raggi): canonicalize url doesn't clean out invalid url chars or fail on |
| // them (e.g. \n) |
| const std::string canon_url = CanonicalizeURL(name.value_or("")); |
| if (canon_url.empty()) { |
| FX_LOGS(ERROR) << "Cannot resolve " << name << " because the url could not be canonicalized"; |
| callback(ZX_ERR_INVALID_ARGS, std::move(binary), std::move(loader)); |
| return; |
| } |
| std::string scheme = GetSchemeFromURL(canon_url); |
| |
| const std::string launcher_type = scheme_map_.LookUp(scheme); |
| if (launcher_type != "package") { |
| FX_LOGS(ERROR) << "Cannot resolve non-packages"; |
| callback(ZX_ERR_NOT_FOUND, std::move(binary), std::move(loader)); |
| return; |
| } |
| |
| FuchsiaPkgUrl pkg_url; |
| if (!pkg_url.Parse(canon_url)) { |
| FX_LOGS(ERROR) << "Cannot load " << canon_url << " because the URL is not valid."; |
| callback(ZX_ERR_INVALID_ARGS, std::move(binary), std::move(loader)); |
| return; |
| } |
| |
| auto trace_id = TRACE_NONCE(); |
| TRACE_ASYNC_BEGIN("appmgr", "Realm::ResolveLoader::LoadUrl", trace_id, "url", canon_url); |
| loader_->LoadUrl(canon_url, [trace_id, callback = std::move(callback), |
| pkg_url](fuchsia::sys::PackagePtr package) mutable { |
| TRACE_ASYNC_END("appmgr", "Realm::ResolveLoader::LoadUrl", trace_id); |
| |
| zx::vmo binary; |
| fidl::InterfaceHandle<fuchsia::ldsvc::Loader> loader; |
| if (!package) { |
| callback(ZX_ERR_NOT_FOUND, std::move(binary), std::move(loader)); |
| return; |
| } |
| if (!package->data) { |
| callback(ZX_ERR_NOT_FOUND, std::move(binary), std::move(loader)); |
| return; |
| } |
| if (!package->directory) { |
| callback(ZX_ERR_NOT_FOUND, std::move(binary), std::move(loader)); |
| return; |
| } |
| fbl::unique_fd dirfd = fsl::OpenChannelAsFileDescriptor(std::move(package->directory)); |
| |
| // The package loader itself is not expected to give us an executable VMO at |
| // package->data, but it is expected to give us a directory handle that is |
| // capable of opening other children with OPEN_RIGHT_EXECUTABLE. |
| // Get the executably-mappable ELF VMO out of the package directory. |
| |
| // Open the resource_path() out of the directory. |
| uint32_t flags = fuchsia::io::OPEN_RIGHT_READABLE | fuchsia::io::OPEN_RIGHT_EXECUTABLE; |
| fbl::unique_fd exec_fd; |
| zx_status_t status = fdio_open_fd_at(dirfd.get(), pkg_url.resource_path().c_str(), flags, |
| exec_fd.reset_and_get_address()); |
| if (status != ZX_OK) { |
| FX_LOGS(ERROR) << "fdio_open_fd_at(" << dirfd.get() << ", " << pkg_url.resource_path().c_str() |
| << ", " << flags << ") failed: " << zx_status_get_string(status); |
| callback(status, std::move(binary), std::move(loader)); |
| return; |
| } |
| |
| // Get the executable VMO. |
| status = fdio_get_vmo_exec(exec_fd.get(), binary.reset_and_get_address()); |
| if (status != ZX_OK) { |
| FX_LOGS(ERROR) << "fdio_get_vmo_exec() failed: " << zx_status_get_string(status); |
| callback(status, std::move(binary), std::move(loader)); |
| return; |
| } |
| |
| // Start up the library loader. |
| zx::status<zx::channel> chan = |
| DynamicLibraryLoader::Start(dirfd.get(), Util::GetLabelFromURL(package->resolved_url)); |
| if (chan.is_error()) { |
| callback(chan.status_value(), std::move(binary), std::move(loader)); |
| return; |
| } |
| loader.set_channel(std::move(chan).value()); |
| callback(ZX_OK, std::move(binary), std::move(loader)); |
| }); |
| } |
| |
| void Realm::CreateComponent(fuchsia::sys::LaunchInfo launch_info, |
| fidl::InterfaceRequest<fuchsia::sys::ComponentController> controller, |
| ComponentObjectCreatedCallback callback) { |
| TRACE_DURATION("appmgr", "Realm::CreateComponent", "launch_info.url", launch_info.url); |
| ComponentRequestWrapper component_request(std::move(controller)); |
| |
| if (launch_info.url.empty()) { |
| FX_LOGS(ERROR) << "Cannot create application because launch_info contains" |
| " an empty url"; |
| component_request.SetReturnValues(kComponentCreationFailed, TerminationReason::URL_INVALID); |
| return; |
| } |
| |
| std::string canon_url = CanonicalizeURL(launch_info.url); |
| if (canon_url.empty()) { |
| FX_LOGS(ERROR) << "Cannot run " << launch_info.url |
| << " because the url could not be canonicalized"; |
| component_request.SetReturnValues(kComponentCreationFailed, TerminationReason::URL_INVALID); |
| return; |
| } |
| launch_info.url = canon_url; |
| std::string scheme = GetSchemeFromURL(canon_url); |
| |
| const std::string launcher_type = scheme_map_.LookUp(scheme); |
| if (launcher_type == "") { |
| component_request.SetReturnValues(kComponentCreationFailed, TerminationReason::URL_INVALID); |
| } else if (launcher_type == "package") { |
| // "package" type doesn't use a runner. |
| |
| // launch_info is moved before LoadUrl() gets at its first argument. |
| auto lu_trace_id = TRACE_NONCE(); |
| TRACE_ASYNC_BEGIN("appmgr", "Realm::CreateComponent::LoadUrl", lu_trace_id, "url", canon_url); |
| std::string url = launch_info.url; |
| loader_->LoadUrl( |
| url, [this, lu_trace_id, launch_info = std::move(launch_info), |
| component_request = std::move(component_request), |
| callback = std::move(callback)](fuchsia::sys::PackagePtr package) mutable { |
| TRACE_ASYNC_END("appmgr", "Realm::CreateComponent::LoadUrl", lu_trace_id); |
| |
| if (package && package->directory) { |
| CreateComponentFromPackage(std::move(package), std::move(launch_info), |
| std::move(component_request), std::move(callback)); |
| } else { |
| component_request.SetReturnValues(kComponentCreationFailed, |
| TerminationReason::PACKAGE_NOT_FOUND); |
| } |
| }); |
| } else { |
| // Component from scheme that maps to a runner. |
| CreateComponentWithRunnerForScheme(launcher_type, std::move(launch_info), |
| std::move(component_request), std::move(callback)); |
| } |
| } |
| |
| Moniker Realm::ComputeMoniker(Realm* realm, const FuchsiaPkgUrl& fp) { |
| std::vector<std::string> realm_path; |
| for (Realm* leaf = realm; leaf != nullptr; leaf = leaf->parent().get()) { |
| realm_path.push_back(leaf->label()); |
| } |
| std::reverse(realm_path.begin(), realm_path.end()); |
| return Moniker{.url = fp.ToString(), .realm_path = std::move(realm_path)}; |
| } |
| |
| void Realm::CreateShell(const std::string& path, zx::channel svc) { |
| TRACE_DURATION("appmgr", "Realm::CreateShell", "path", path); |
| if (!svc) |
| return; |
| |
| SandboxMetadata sandbox; |
| sandbox.AddFeature("deprecated-shell"); |
| |
| NamespaceBuilder builder = NamespaceBuilder(appmgr_config_dir_.duplicate(), path); |
| builder.AddServices(std::move(svc)); |
| zx_status_t status; |
| status = builder.AddSandbox(sandbox, [this] { return OpenInfoDir(); }); |
| if (status != ZX_OK) |
| return; |
| |
| zx::vmo executable; |
| fbl::unique_fd fd; |
| status = fdio_open_fd(path.c_str(), |
| fuchsia::io::OPEN_RIGHT_READABLE | fuchsia::io::OPEN_RIGHT_EXECUTABLE, |
| fd.reset_and_get_address()); |
| if (status != ZX_OK) { |
| return; |
| } |
| status = fdio_get_vmo_exec(fd.get(), executable.reset_and_get_address()); |
| if (status != ZX_OK) { |
| return; |
| } |
| |
| zx::job child_job; |
| status = zx::job::create(job_, 0u, &child_job); |
| if (status != ZX_OK) |
| return; |
| |
| std::vector<std::string> env_vars; |
| fuchsia::sys::LaunchInfo launch_info; |
| launch_info.url = path; |
| zx::process process = CreateProcess(child_job, std::move(executable), path, env_vars, |
| std::move(launch_info), zx::channel(), builder.Build()); |
| } |
| |
| std::unique_ptr<EnvironmentControllerImpl> Realm::ExtractChild(Realm* child) { |
| auto it = children_.find(child); |
| if (it == children_.end()) { |
| return nullptr; |
| } |
| auto controller = std::move(it->second); |
| |
| // update hub |
| hub_.RemoveRealm(child->HubInfo()); |
| |
| children_.erase(it); |
| return controller; |
| } |
| |
| std::shared_ptr<ComponentControllerImpl> Realm::ExtractComponent( |
| ComponentControllerImpl* controller) { |
| auto it = applications_.find(controller); |
| if (it == applications_.end()) { |
| return nullptr; |
| } |
| auto application = std::move(it->second); |
| |
| NotifyComponentStopped(application->url(), application->label(), application->hub_instance_id()); |
| |
| // update hub |
| hub_.RemoveComponent(application->HubInfo()); |
| |
| applications_.erase(it); |
| return application; |
| } |
| |
| void Realm::AddBinding(fidl::InterfaceRequest<fuchsia::sys::Environment> environment) { |
| default_namespace_->AddBinding(std::move(environment)); |
| } |
| |
| void Realm::CreateComponentWithRunnerForScheme(std::string runner_url, |
| fuchsia::sys::LaunchInfo launch_info, |
| ComponentRequestWrapper component_request, |
| ComponentObjectCreatedCallback callback) { |
| TRACE_DURATION("appmgr", "Realm::CreateComponentWithRunnerForScheme", "runner_url", runner_url, |
| "launch_info.url", launch_info.url); |
| |
| fuchsia::sys::Package package; |
| package.resolved_url = launch_info.url; |
| |
| fuchsia::sys::StartupInfo startup_info; |
| startup_info.launch_info = std::move(launch_info); |
| NamespaceBuilder builder = |
| NamespaceBuilder(appmgr_config_dir_.duplicate(), startup_info.launch_info.url); |
| startup_info.flat_namespace = builder.BuildForRunner(); |
| |
| auto* runner = GetOrCreateRunner(runner_url); |
| if (runner == nullptr) { |
| FX_LOGS(ERROR) << "Cannot create " << runner_url << " to run " << startup_info.launch_info.url; |
| component_request.SetReturnValues(kComponentCreationFailed, TerminationReason::RUNNER_FAILED); |
| return; |
| } |
| |
| fxl::RefPtr<Namespace> ns = |
| Namespace::CreateChildNamespace(default_namespace_, weak_ptr(), nullptr, nullptr); |
| |
| if (ns.get() == nullptr) { |
| component_request.SetReturnValues(-1, fuchsia::sys::TerminationReason::UNSUPPORTED); |
| return; |
| } |
| |
| fidl::InterfaceRequest<fuchsia::sys::ComponentController> controller; |
| component_request.Extract(&controller); |
| runner->StartComponent(std::move(package), std::move(startup_info), std::move(ns), |
| std::move(controller), std::nullopt); |
| } |
| |
| void Realm::CreateComponentFromPackage(fuchsia::sys::PackagePtr package, |
| fuchsia::sys::LaunchInfo launch_info, |
| ComponentRequestWrapper component_request, |
| ComponentObjectCreatedCallback callback) { |
| TRACE_DURATION("appmgr", "Realm::CreateComponentFromPackage", "package.resolved_url", |
| package->resolved_url, "launch_info.url", launch_info.url); |
| fbl::unique_fd pkg_fd = fsl::OpenChannelAsFileDescriptor(std::move(package->directory)); |
| |
| // Parse cmx manifest file, if it's there. |
| CmxMetadata cmx; |
| std::string cmx_path; |
| FuchsiaPkgUrl fp; |
| bool is_fuchsia_pkg_url = false; |
| if (fp.Parse(package->resolved_url)) { |
| if (!fp.resource_path().empty()) { |
| // If the url has a resource, assume that's the cmx. |
| cmx_path = fp.resource_path(); |
| |
| // The URL is fuchsia-pkg iff it has a resource. |
| // TODO(fxbug.dev/4053): Remove this logic once all URLs are fuchsia-pkg. |
| is_fuchsia_pkg_url = true; |
| } else { |
| // It's possible the url does not have a resource, in which case either |
| // the cmx exists at meta/<package_name.cmx> or it does not exist. |
| cmx_path = fp.GetDefaultComponentCmxPath(); |
| } |
| } else { |
| FX_LOGS(ERROR) << "invalid component url: " << package->resolved_url; |
| component_request.SetReturnValues(kComponentCreationFailed, TerminationReason::INTERNAL_ERROR); |
| return; |
| } |
| TRACE_DURATION_BEGIN("appmgr", "Realm::CreateComponentFromPackage:IsFileAt", "cmx_path", |
| cmx_path); |
| if (!cmx_path.empty() && files::IsFileAt(pkg_fd.get(), cmx_path)) { |
| TRACE_DURATION_END("appmgr", "Realm::CreateComponentFromPackage:IsFileAt"); |
| json::JSONParser json_parser; |
| { |
| TRACE_DURATION("appmgr", "Realm::CreateComponentFromPackage:ParseFromFileAt", "cmx_path", |
| cmx_path); |
| if (!cmx.ParseFromFileAt(pkg_fd.get(), cmx_path, &json_parser)) { |
| FX_LOGS(ERROR) << "cmx file failed to parse: " << json_parser.error_str(); |
| component_request.SetReturnValues(kComponentCreationFailed, |
| TerminationReason::INTERNAL_ERROR); |
| return; |
| } |
| } |
| } else { |
| TRACE_DURATION_END("appmgr", "Realm::CreateComponentFromPackage:IsFileAt"); |
| FX_LOGS(ERROR) << "Component " << package->resolved_url |
| << " does not have a component manifest (a.k.a. cmx file)! " |
| << "Please add a cmx file to your component. " |
| << "https://fuchsia.dev/fuchsia-src/concepts/storage/" |
| << "package_metadata#component_manifest"; |
| component_request.SetReturnValues(kComponentCreationFailed, TerminationReason::INTERNAL_ERROR); |
| return; |
| } |
| |
| if (!is_fuchsia_pkg_url) { |
| FX_LOGS(ERROR) << "Component could not be launched from " << package->resolved_url |
| << " because it is not a valid Fuchsia component URL!"; |
| component_request.SetReturnValues(kComponentCreationFailed, TerminationReason::INTERNAL_ERROR); |
| return; |
| } |
| |
| RuntimeMetadata runtime; |
| // If meta/*.cmx has runtime data, get it. |
| if (!cmx.runtime_meta().IsNull()) { |
| runtime = cmx.runtime_meta(); |
| } |
| |
| zx::vmo executable; |
| std::string app_argv0; |
| fidl::VectorPtr<fuchsia::sys::ProgramMetadata> program_metadata; |
| const ProgramMetadata& program = cmx.program_meta(); |
| |
| if (launch_info.arguments.has_value()) { |
| launch_info.arguments->insert(launch_info.arguments->begin(), program.args().begin(), |
| program.args().end()); |
| } else { |
| launch_info.arguments = program.args(); |
| } |
| |
| zx::channel loader_service; |
| if (runtime.IsNull()) { |
| // If we cannot parse a runtime from either .cmx or deprecated_runtime, then |
| // we fall back to the default runner, which is running an ELF binary or |
| // shell script. |
| const std::string bin_path = program.IsBinaryNull() ? kAppPath : program.binary(); |
| |
| app_argv0 = fxl::Concatenate({kAppArgv0Prefix, bin_path}); |
| TRACE_DURATION_BEGIN("appmgr", "Realm::CreateComponentFromPackage:VmoFromFilenameAt", |
| "bin_path", bin_path); |
| zx_status_t status; |
| fbl::unique_fd elf_fd; |
| status = fdio_open_fd_at(pkg_fd.get(), bin_path.c_str(), |
| fuchsia::io::OPEN_RIGHT_READABLE | fuchsia::io::OPEN_RIGHT_EXECUTABLE, |
| elf_fd.reset_and_get_address()); |
| if (status == ZX_OK) { |
| status = fdio_get_vmo_exec(elf_fd.get(), executable.reset_and_get_address()); |
| } |
| TRACE_DURATION_END("appmgr", "Realm::CreateComponentFromPackage:VmoFromFilenameAt"); |
| if (status != ZX_OK) { |
| FX_LOGS(ERROR) << "Failed to open '" << package->resolved_url << "' program.binary path: '" |
| << bin_path << "', with status: " << status; |
| component_request.SetReturnValues(kComponentCreationFailed, |
| TerminationReason::INTERNAL_ERROR); |
| return; |
| } |
| |
| { |
| zx::status<zx::channel> status = |
| DynamicLibraryLoader::Start(pkg_fd.get(), Util::GetLabelFromURL(launch_info.url)); |
| if (status.is_error()) { |
| component_request.SetReturnValues(kComponentCreationFailed, |
| TerminationReason::INTERNAL_ERROR); |
| return; |
| } |
| loader_service = std::move(status).value(); |
| } |
| } else { |
| // Read 'data' path from cmx, or assume to be /pkg/data/<component-name>. |
| std::string data_path = |
| program.IsDataNull() ? kDataPathPrefix + fp.package_name() : program.data(); |
| // Pass a {"data", "data/<component-name>"} pair through StartupInfo, so |
| // components can identify their directory under /pkg/data. |
| fuchsia::sys::ProgramMetadata pg; |
| pg.key = kDataKey; |
| pg.value = data_path; |
| program_metadata.emplace({pg}); |
| // Also add binary path |
| if (!program.IsBinaryNull()) { |
| pg.key = kBinaryKey; |
| pg.value = program.binary(); |
| program_metadata->push_back(pg); |
| } |
| // Add in whatever else is in the original specification |
| for (const auto& attribute : program.unknown_attributes()) { |
| pg.key = attribute.first; |
| pg.value = attribute.second; |
| program_metadata->push_back(pg); |
| } |
| } |
| |
| // We want two handles to the package, one to put in the component's namespace |
| // and one to put in the hub. |
| zx::channel pkg = fsl::TransferChannelFromFileDescriptor(std::move(pkg_fd)); |
| zx::channel pkg_clone; |
| if (pkg.is_valid()) { |
| pkg_clone = zx::channel(fdio_service_clone(pkg.get())); |
| } |
| |
| // Note that |builder| is only used in the else block below. It is left here |
| // because we would like to use it everywhere once fxbug.dev/28222 is fixed. |
| NamespaceBuilder builder = NamespaceBuilder(appmgr_config_dir_.duplicate(), fp.ToString()); |
| builder.AddPackage(std::move(pkg)); |
| |
| // If meta/*.cmx exists, attempt to read sandbox data from it. |
| const std::vector<std::string>* service_allowlist = nullptr; |
| std::vector<zx_policy_basic_v2_t> policies; |
| |
| if (!cmx.sandbox_meta().IsNull()) { |
| const auto& sandbox = cmx.sandbox_meta(); |
| service_allowlist = &sandbox.services(); |
| |
| builder.AddConfigData(sandbox, fp.package_name()); |
| |
| zx_status_t status = builder.AddSandbox( |
| sandbox, |
| /*hub_directory_factory=*/[this] { return OpenInfoDir(); }, |
| /*isolated_data_path_factory=*/ |
| [&] { return InitIsolatedPathForComponentInstance(fp, internal::StorageType::DATA); }, |
| [&] { return InitIsolatedPathForComponentInstance(fp, internal::StorageType::CACHE); }, |
| [&] { return InitIsolatedPathForComponentInstance(fp, internal::StorageType::TEMP); }); |
| if (status != ZX_OK) { |
| component_request.SetReturnValues(kComponentCreationFailed, TerminationReason::ACCESS_DENIED); |
| return; |
| } |
| |
| // It is critical that if nothing is returned the component does not lanuch. |
| PolicyChecker policy_checker(appmgr_config_dir_.duplicate()); |
| std::optional<SecurityPolicy> security_policy = policy_checker.Check(sandbox, fp); |
| if (!security_policy.has_value()) { |
| component_request.SetReturnValues(kComponentCreationFailed, TerminationReason::UNSUPPORTED); |
| return; |
| } |
| |
| if (!security_policy->enable_ambient_executable) { |
| policies.push_back(zx_policy_basic_v2_t{.condition = ZX_POL_AMBIENT_MARK_VMO_EXEC, |
| .action = ZX_POL_ACTION_DENY, |
| .flags = ZX_POL_OVERRIDE_DENY}); |
| } |
| |
| fxl::RefPtr<Namespace> ns = Namespace::CreateChildNamespace( |
| default_namespace_, weak_ptr(), std::move(launch_info.additional_services), |
| service_allowlist); |
| |
| if (ns.get() == nullptr) { |
| component_request.SetReturnValues(-1, fuchsia::sys::TerminationReason::UNSUPPORTED); |
| return; |
| } |
| |
| // Add a component event provider for v1 archivists/observers. |
| if (security_policy->enable_component_event_provider) { |
| ns->MaybeAddComponentEventProvider(); |
| } |
| |
| ns->set_component_moniker(ComputeMoniker(this, fp)); |
| zx::channel svc = ns->OpenServicesAsDirectory(); |
| if (!svc) { |
| component_request.SetReturnValues(kComponentCreationFailed, |
| TerminationReason::INTERNAL_ERROR); |
| return; |
| } |
| builder.AddServices(std::move(svc)); |
| |
| // Add the custom namespace. |
| // Note that this must be the last |builder| step adding entries to the |
| // namespace so that we can filter out entries already added in previous |
| // steps. |
| builder.AddFlatNamespace(std::move(launch_info.flat_namespace)); |
| |
| if (runtime.IsNull()) { |
| // Use the default runner: ELF binaries. |
| CreateElfBinaryComponentFromPackage( |
| std::move(launch_info), std::move(executable), app_argv0, program.env_vars(), |
| std::move(loader_service), builder.Build(), std::move(component_request), std::move(ns), |
| policies, std::move(callback), std::move(pkg_clone)); |
| } else { |
| // Use other component runners. |
| CreateRunnerComponentFromPackage(std::move(package), std::move(launch_info), runtime, |
| builder.BuildForRunner(), std::move(component_request), |
| std::move(ns), std::move(program_metadata), |
| std::move(pkg_clone)); |
| } |
| } |
| } |
| |
| void Realm::CreateElfBinaryComponentFromPackage( |
| fuchsia::sys::LaunchInfo launch_info, zx::vmo executable, const std::string& app_argv0, |
| const std::vector<std::string>& env_vars, zx::channel loader_service, |
| fdio_flat_namespace_t* flat, ComponentRequestWrapper component_request, |
| fxl::RefPtr<Namespace> ns, const std::vector<zx_policy_basic_v2_t>& policies, |
| ComponentObjectCreatedCallback callback, zx::channel package_handle) { |
| TRACE_DURATION("appmgr", "Realm::CreateElfBinaryComponentFromPackage", "launch_info.url", |
| launch_info.url); |
| |
| zx::job child_job; |
| zx_status_t status = zx::job::create(job_, 0u, &child_job); |
| if (status != ZX_OK) { |
| return; |
| } |
| if (!policies.empty()) { |
| status = child_job.set_policy(ZX_JOB_POL_RELATIVE, ZX_JOB_POL_BASIC_V2, policies.data(), |
| policies.size()); |
| if (status != ZX_OK) { |
| return; |
| } |
| } |
| |
| const std::string args = Util::GetArgsString(launch_info.arguments); |
| const std::string url = launch_info.url; // Keep a copy before moving it. |
| auto channels = Util::BindDirectory(&launch_info); |
| zx::process process = CreateProcess(child_job, std::move(executable), app_argv0, env_vars, |
| std::move(launch_info), std::move(loader_service), flat); |
| |
| if (process) { |
| fidl::InterfaceRequest<fuchsia::sys::ComponentController> controller; |
| |
| component_request.Extract(&controller); |
| auto application = std::make_shared<ComponentControllerImpl>( |
| std::move(controller), this, std::move(child_job), std::move(process), url, std::move(args), |
| Util::GetLabelFromURL(url), std::move(ns), std::move(channels.exported_dir), |
| std::move(channels.client_request), std::move(package_handle)); |
| // update hub |
| hub_.AddComponent(application->HubInfo()); |
| ComponentControllerImpl* key = application.get(); |
| if (callback != nullptr) { |
| callback(application); |
| } |
| fuchsia::sys::internal::SourceIdentity component_info; |
| component_info.set_component_name(application->label()); |
| component_info.set_component_url(application->url()); |
| component_info.set_instance_id(application->hub_instance_id()); |
| RegisterJobForCrashIntrospection(application->job(), std::move(component_info)); |
| NotifyComponentStarted(application->url(), application->label(), |
| application->hub_instance_id()); |
| applications_.emplace(key, std::move(application)); |
| } |
| } |
| |
| void Realm::CreateRunnerComponentFromPackage( |
| fuchsia::sys::PackagePtr package, fuchsia::sys::LaunchInfo launch_info, |
| RuntimeMetadata& runtime, fuchsia::sys::FlatNamespace flat, |
| ComponentRequestWrapper component_request, fxl::RefPtr<Namespace> ns, |
| fidl::VectorPtr<fuchsia::sys::ProgramMetadata> program_metadata, zx::channel package_handle) { |
| TRACE_DURATION("appmgr", "Realm::CreateRunnerComponentFromPackage", "package.resolved_url", |
| package->resolved_url, "launch_info.url", launch_info.url); |
| |
| fuchsia::sys::Package inner_package; |
| inner_package.resolved_url = package->resolved_url; |
| |
| fuchsia::sys::StartupInfo startup_info; |
| startup_info.launch_info = std::move(launch_info); |
| startup_info.flat_namespace = std::move(flat); |
| startup_info.program_metadata = std::move(program_metadata); |
| |
| auto* runner = GetOrCreateRunner(runtime.runner()); |
| if (runner == nullptr) { |
| FX_LOGS(ERROR) << "Cannot create " << runner << " to run " << startup_info.launch_info.url; |
| component_request.SetReturnValues(kComponentCreationFailed, TerminationReason::INTERNAL_ERROR); |
| return; |
| } |
| |
| fidl::InterfaceRequest<fuchsia::sys::ComponentController> controller; |
| component_request.Extract(&controller); |
| runner->StartComponent(std::move(inner_package), std::move(startup_info), std::move(ns), |
| std::move(controller), std::move(package_handle)); |
| } |
| |
| RunnerHolder* Realm::GetOrCreateRunner(const std::string& runner) { |
| // Determine the realm whose runner should be used. |
| auto realm_runner = GetRunnerRealm(); |
| |
| auto result = realm_runner->runners_.emplace(runner, nullptr); |
| if (result.second) { |
| zx::channel request; |
| auto runner_services = sys::ServiceDirectory::CreateWithRequest(&request); |
| fuchsia::sys::ComponentControllerPtr runner_controller; |
| fuchsia::sys::LaunchInfo runner_launch_info; |
| runner_launch_info.url = runner; |
| runner_launch_info.directory_request = std::move(request); |
| result.first->second = std::make_unique<RunnerHolder>( |
| std::move(runner_services), std::move(runner_controller), std::move(runner_launch_info), |
| realm_runner, [realm_runner, runner] { realm_runner->runners_.erase(runner); }); |
| |
| } else if (!result.first->second) { |
| // There was a cycle in the runner graph. |
| FX_LOGS(ERROR) << "Detected a cycle in the runner graph for " << runner << "."; |
| return nullptr; |
| } |
| |
| return result.first->second.get(); |
| } |
| |
| Realm* Realm::GetRunnerRealm() { |
| auto realm = this; |
| |
| while (realm->use_parent_runners_ && realm->parent_) { |
| realm = realm->parent_.get(); |
| } |
| |
| return realm; |
| } |
| |
| zx_status_t Realm::BindFirstNestedRealmSvc(zx::channel channel) { |
| if (parent_) { |
| return ZX_ERR_NOT_SUPPORTED; |
| } |
| return fdio_service_clone_to(first_nested_realm_svc_client_.get(), channel.release()); |
| } |
| |
| // A component instance's storage directory is in one of two places: |
| // (a) A directory key'd using component instance ID, if it has one. |
| // (b) A directory computed using fn(realm_path, component URL) |
| // |
| // If a component is assigned an instance ID while it already has a storage |
| // directory under (b), its storage directory is moved to (a). |
| fit::result<std::string, zx_status_t> Realm::InitIsolatedPathForComponentInstance( |
| const FuchsiaPkgUrl& fp, internal::StorageType storage_type) { |
| // The subdirectory of the root data directory to use persistent storage |
| // This applies only to components with an instance ID. |
| constexpr char kInstanceIdPersistentSubdir[] = "persistent"; |
| |
| // Compute directory path based on realm (b). |
| std::string old_path; |
| switch (storage_type) { |
| case internal::StorageType::DATA: |
| old_path = files::JoinPath(data_path(), ComponentUrlToPathComponent(fp)); |
| break; |
| case internal::StorageType::CACHE: |
| old_path = files::JoinPath(cache_path(), ComponentUrlToPathComponent(fp)); |
| break; |
| case internal::StorageType::TEMP: |
| old_path = files::JoinPath(temp_path(), ComponentUrlToPathComponent(fp)); |
| break; |
| }; |
| |
| std::string path = old_path; |
| // if (a) is possible, use it instead, and move (b) to (a) if needed. |
| std::string instance_id_path; |
| auto instance_id = component_id_index_->LookupMoniker(Realm::ComputeMoniker(this, fp)); |
| if (instance_id) { |
| auto* root_realm = GetRootRealm(this); |
| switch (storage_type) { |
| case internal::StorageType::DATA: |
| instance_id_path = |
| files::JoinPath(files::JoinPath(root_realm->data_path(), kInstanceIdPersistentSubdir), |
| instance_id.value()); |
| break; |
| case internal::StorageType::CACHE: |
| instance_id_path = files::JoinPath(root_realm->cache_path(), instance_id.value()); |
| break; |
| case internal::StorageType::TEMP: |
| instance_id_path = files::JoinPath(root_realm->temp_path(), instance_id.value()); |
| break; |
| }; |
| path = instance_id_path; |
| |
| if (files::IsDirectory(old_path)) { |
| if (!files::CreateDirectory(files::GetDirectoryName(instance_id_path)) || |
| rename(old_path.c_str(), instance_id_path.c_str()) != 0) { |
| FX_LOGS(ERROR) << "Unable to move component storage directory " << old_path |
| << " to be the new instance ID directory " << instance_id_path |
| << ". errno = " << strerror(errno) |
| << ". Continuing to use moniker based storage directory."; |
| path = old_path; |
| } else { |
| FX_LOGS(INFO) << "Moved component storage directory from " << old_path << " to " |
| << instance_id_path; |
| } |
| } |
| } |
| if (!instance_id && storage_type == internal::StorageType::DATA) { |
| if (component_id_index_->restrict_isolated_persistent_storage()) { |
| FX_LOGS(ERROR) << "Component " << fp.ToString() |
| << " cannot be created because it is using isolated-persistent-storage, but " |
| "is not listed in the component instance ID index."; |
| return fit::error(ZX_ERR_ACCESS_DENIED); |
| } else { |
| FX_LOGS(WARNING) << "Component " << fp.ToString() |
| << " is using isolated-persistent-storage, but is not listed in the " |
| "component instance ID index."; |
| } |
| } |
| |
| // Ensure directory path exists. |
| if (!files::IsDirectory(path) && !files::CreateDirectory(path)) { |
| FX_LOGS(ERROR) << "Failed to create data directory " << path; |
| return fit::ok(""); |
| } |
| |
| return fit::ok(path); |
| } |
| |
| void Realm::NotifyComponentStarted(const std::string& component_url, |
| const std::string& component_name, |
| const std::string& instance_id) { |
| auto notify_data = GetEventNotificationInfo(component_url, component_name, instance_id); |
| if (notify_data.provider) { |
| notify_data.provider->NotifyComponentStarted(std::move(notify_data.component)); |
| } |
| } |
| |
| void Realm::NotifyComponentDiagnosticsDirReady( |
| const std::string& component_url, const std::string& component_name, |
| const std::string& instance_id, fidl::InterfaceHandle<fuchsia::io::Directory> directory) { |
| auto notify_data = GetEventNotificationInfo(component_url, component_name, instance_id); |
| if (notify_data.provider) { |
| notify_data.provider->NotifyComponentDirReady(std::move(notify_data.component), |
| std::move(directory)); |
| } |
| } |
| |
| void Realm::NotifyComponentStopped(const std::string& component_url, |
| const std::string& component_name, |
| const std::string& instance_id) { |
| auto notify_data = GetEventNotificationInfo(component_url, component_name, instance_id); |
| if (notify_data.provider) { |
| notify_data.provider->NotifyComponentStopped(std::move(notify_data.component)); |
| } |
| } |
| |
| internal::EventNotificationInfo Realm::GetEventNotificationInfo(const std::string& component_url, |
| const std::string& component_name, |
| const std::string& instance_id) { |
| ComponentEventProviderImpl* provider = nullptr; |
| std::vector<std::string> relative_realm_path; |
| |
| // If this realm has a ComponentEventProvider, then the relative_realm_path should be empty and |
| // the provider attached to this realm should be used. |
| if (this->component_event_provider_) { |
| provider = this->component_event_provider_.get(); |
| } else { |
| relative_realm_path.push_back(label_); |
| auto realm = weak_ptr(); |
| |
| // Stop traversing the path to the root once a child of the root realm "app" is found. |
| while (realm && realm->parent_ && !provider) { |
| realm = realm->parent_; |
| if (realm->component_event_provider_) { |
| provider = realm->component_event_provider_.get(); |
| } else { |
| relative_realm_path.push_back(realm->label_); |
| } |
| } |
| std::reverse(relative_realm_path.begin(), relative_realm_path.end()); |
| |
| // NOTE: the archivist used to be in the sys realm of the v1 componments world. Now it's a |
| // v2 component who is a sibling of appmgr, therefore realm paths (which are relative to |
| // the archivist position) will be prefixed by `sys`. To avoid a soft migration of clients |
| // depending on the moniker not containing `sys` we strip it. To continue allowing tests using |
| // an observer to continue creating environments named "sys" we only strip this prefix if it's |
| // the actual sys realm, this is, we stopped at the root realm. |
| if (relative_realm_path.size() > 0 && (relative_realm_path[0].compare("sys") == 0) && realm && |
| (realm->label_.compare(internal::kRootLabel) == 0)) { |
| relative_realm_path.erase(relative_realm_path.begin()); |
| } |
| } |
| |
| fuchsia::sys::internal::SourceIdentity identity; |
| identity.set_component_url(component_url); |
| identity.set_component_name(component_name); |
| identity.set_instance_id(instance_id); |
| identity.set_realm_path(relative_realm_path); |
| return {.provider = provider, .component = std::move(identity)}; |
| } |
| |
| zx_status_t Realm::BindComponentEventProvider( |
| fidl::InterfaceRequest<fuchsia::sys::internal::ComponentEventProvider> request) { |
| if (!component_event_provider_) { |
| component_event_provider_ = |
| std::make_unique<ComponentEventProviderImpl>(weak_ptr(), async_get_default_dispatcher()); |
| } |
| auto status = component_event_provider_->Connect(std::move(request)); |
| return status; |
| } |
| |
| bool Realm::HasComponentEventListenerBound() { |
| return component_event_provider_ && component_event_provider_->listener_bound(); |
| } |
| |
| void Realm::RegisterJobForCrashIntrospection( |
| const zx::job& job, fuchsia::sys::internal::SourceIdentity component_info) { |
| component_info.mutable_realm_path()->push_back(label_); |
| if (likely(parent_)) { |
| parent_->RegisterJobForCrashIntrospection(job, std::move(component_info)); |
| } else if (crash_introspector_) { |
| auto* path = component_info.mutable_realm_path(); |
| std::reverse(path->begin(), path->end()); |
| crash_introspector_->RegisterJob(job, std::move(component_info)); |
| } else { |
| FX_LOGS(ERROR) << "Cannot find parent or crash introspector for realm: " << label_; |
| } |
| } |
| |
| void Realm::ShutdownNamespace(ShutdownNamespaceCallback callback) { |
| job_.kill(); |
| default_namespace_->FlushAndShutdown(default_namespace_, std::move(callback)); |
| } |
| |
| } // namespace component |