blob: 0ab9e9b4de650064bafb9661fe789a264b16d855 [file] [log] [blame]
// 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 "garnet/bin/appmgr/realm.h"
#include <fcntl.h>
#include <fdio/namespace.h>
#include <fdio/util.h>
#include <launchpad/launchpad.h>
#include <lib/async/default.h>
#include <lib/zx/process.h>
#include <unistd.h>
#include <zircon/process.h>
#include <zircon/processargs.h>
#include <zircon/status.h>
#include <utility>
#include "garnet/bin/appmgr/dynamic_library_loader.h"
#include "garnet/bin/appmgr/namespace_builder.h"
#include "garnet/bin/appmgr/realm_hub_holder.h"
#include "garnet/bin/appmgr/runtime_metadata.h"
#include "garnet/bin/appmgr/url_resolver.h"
#include "garnet/lib/far/format.h"
#include "lib/app/cpp/connect.h"
#include "lib/fsl/handles/object_info.h"
#include "lib/fsl/io/fd.h"
#include "lib/fsl/vmo/file.h"
#include "lib/fxl/files/file.h"
#include "lib/fxl/functional/auto_call.h"
#include "lib/fxl/functional/make_copyable.h"
#include "lib/fxl/strings/string_printf.h"
#include "lib/svc/cpp/services.h"
namespace component {
namespace {
constexpr zx_rights_t kChildJobRights = ZX_RIGHTS_BASIC | ZX_RIGHTS_IO;
constexpr char kNumberedLabelFormat[] = "env-%d";
constexpr char kAppPath[] = "bin/app";
constexpr char kAppArv0[] = "/pkg/bin/app";
constexpr char kLegacyFlatExportedDirPath[] = "meta/legacy_flat_exported_dir";
constexpr char kRuntimePath[] = "meta/runtime";
constexpr char kSandboxPath[] = "meta/sandbox";
enum class LaunchType {
kProcess,
kArchive,
};
std::vector<const char*> GetArgv(const std::string& argv0,
const ApplicationLaunchInfo& launch_info) {
std::vector<const char*> argv;
argv.reserve(launch_info.arguments->size() + 1);
argv.push_back(argv0.c_str());
for (const auto& argument : *launch_info.arguments)
argv.push_back(argument.get().c_str());
return argv;
}
std::string GetLabelFromURL(const std::string& url) {
size_t last_slash = url.rfind('/');
if (last_slash == std::string::npos || last_slash + 1 == url.length())
return url;
return url.substr(last_slash + 1);
}
void PushFileDescriptor(component::FileDescriptorPtr fd, int new_fd,
std::vector<uint32_t>* ids,
std::vector<zx_handle_t>* handles) {
if (!fd)
return;
if (fd->type0) {
ids->push_back(PA_HND(PA_HND_TYPE(fd->type0), new_fd));
handles->push_back(fd->handle0.release());
}
if (fd->type1) {
ids->push_back(PA_HND(PA_HND_TYPE(fd->type1), new_fd));
handles->push_back(fd->handle1.release());
}
if (fd->type2) {
ids->push_back(PA_HND(PA_HND_TYPE(fd->type2), new_fd));
handles->push_back(fd->handle2.release());
}
}
zx::process CreateProcess(const zx::job& job, fsl::SizedVmo data,
const std::string& argv0,
ApplicationLaunchInfo launch_info,
zx::channel loader_service,
fdio_flat_namespace_t* flat) {
if (!data)
return zx::process();
std::string label = GetLabelFromURL(launch_info.url);
std::vector<const char*> argv = GetArgv(argv0, launch_info);
std::vector<uint32_t> ids;
std::vector<zx_handle_t> handles;
zx::channel directory_request = std::move(launch_info.directory_request);
if (directory_request) {
ids.push_back(PA_DIRECTORY_REQUEST);
handles.push_back(directory_request.release());
}
PushFileDescriptor(std::move(launch_info.out), STDOUT_FILENO, &ids, &handles);
PushFileDescriptor(std::move(launch_info.err), STDERR_FILENO, &ids, &handles);
for (size_t i = 0; i < flat->count; ++i) {
ids.push_back(flat->type[i]);
handles.push_back(flat->handle[i]);
}
data.vmo().set_property(ZX_PROP_NAME, label.data(), label.size());
// TODO(abarth): We probably shouldn't pass environ, but currently this
// is very useful as a way to tell the loader in the child process to
// print out load addresses so we can understand crashes.
launchpad_t* lp = nullptr;
launchpad_create(job.get(), label.c_str(), &lp);
launchpad_clone(lp, LP_CLONE_ENVIRON);
launchpad_clone_fd(lp, STDIN_FILENO, STDIN_FILENO);
if (!launch_info.out)
launchpad_clone_fd(lp, STDOUT_FILENO, STDOUT_FILENO);
if (!launch_info.err)
launchpad_clone_fd(lp, STDERR_FILENO, STDERR_FILENO);
if (loader_service)
launchpad_use_loader_service(lp, loader_service.release());
launchpad_set_args(lp, argv.size(), argv.data());
launchpad_set_nametable(lp, flat->count, flat->path);
launchpad_add_handles(lp, handles.size(), handles.data(), ids.data());
launchpad_load_from_vmo(lp, data.vmo().release());
zx_handle_t proc;
const char* errmsg;
zx_handle_t status = launchpad_go(lp, &proc, &errmsg);
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Cannot run executable " << label << " due to error "
<< status << " (" << zx_status_get_string(status)
<< "): " << errmsg;
return zx::process();
}
return zx::process(proc);
}
LaunchType Classify(const zx::vmo& data, std::string* runner) {
if (!data)
return LaunchType::kProcess;
std::string magic(archive::kMagicLength, '\0');
zx_status_t status = data.read(&magic[0], 0, magic.length());
if (status != ZX_OK)
return LaunchType::kProcess;
if (memcmp(magic.data(), &archive::kMagic, sizeof(archive::kMagic)) == 0)
return LaunchType::kArchive;
return LaunchType::kProcess;
}
struct ExportedDirChannels {
// The client side of the channel serving connected application's exported
// dir.
zx::channel exported_dir;
// The server side of our client's |ApplicationLaunchInfo.directory_request|.
zx::channel client_request;
};
ExportedDirChannels BindDirectory(ApplicationLaunchInfo* launch_info) {
zx::channel exported_dir_server, exported_dir_client;
zx_status_t status =
zx::channel::create(0u, &exported_dir_server, &exported_dir_client);
if (status != ZX_OK) {
FXL_LOG(ERROR) << "Failed to create channel for service directory: status="
<< status;
return {zx::channel(), zx::channel()};
}
auto client_request = std::move(launch_info->directory_request);
launch_info->directory_request = std::move(exported_dir_server);
return {std::move(exported_dir_client), std::move(client_request)};
}
std::string GetArgsString(
const ::fidl::VectorPtr<::fidl::StringPtr>& arguments) {
std::string args = "";
if (!arguments->empty()) {
std::ostringstream buf;
std::copy(arguments->begin(), arguments->end() - 1,
std::ostream_iterator<std::string>(buf, " "));
buf << *arguments->rbegin();
args = buf.str();
}
return args;
}
} // namespace
uint32_t Realm::next_numbered_label_ = 1u;
Realm::Realm(Realm* parent, zx::channel host_directory, fidl::StringPtr label)
: parent_(parent),
default_namespace_(
fxl::MakeRefCounted<Namespace>(nullptr, this, nullptr)),
hub_(fbl::AdoptRef(new fs::PseudoDir())),
info_vfs_(async_get_default()) {
// parent_ is null if this is the root application environment. if so, we
// derive from the application manager's job.
zx_handle_t parent_job =
parent_ != nullptr ? parent_->job_.get() : zx_job_default();
// init svc service channel for root application environment
if (parent_ == nullptr) {
FXL_CHECK(zx::channel::create(0, &svc_channel_server_,
&svc_channel_client_) == ZX_OK);
}
FXL_CHECK(zx::job::create(parent_job, 0u, &job_) == ZX_OK);
FXL_CHECK(job_.duplicate(kChildJobRights, &job_for_child_) == ZX_OK);
koid_ = std::to_string(fsl::GetKoid(job_.get()));
if (label->size() == 0)
label_ = fxl::StringPrintf(kNumberedLabelFormat, next_numbered_label_++);
else
label_ = label.get().substr(0, component::kLabelMaxLength);
fsl::SetObjectName(job_.get(), label_);
hub_.SetName(label_);
hub_.SetJobId(koid_);
default_namespace_->services().set_backing_dir(std::move(host_directory));
ServiceProviderPtr service_provider;
default_namespace_->services().AddBinding(service_provider.NewRequest());
loader_ = ConnectToService<Loader>(service_provider.get());
}
Realm::~Realm() { job_.kill(); }
zx::channel Realm::OpenRootInfoDir() {
// TODO: define /hub access policy: CP-26
Realm* root_realm = this;
while (root_realm->parent() != nullptr) {
root_realm = root_realm->parent();
}
zx::channel h1, h2;
if (zx::channel::create(0, &h1, &h2) < 0) {
return zx::channel();
}
if (info_vfs_.ServeDirectory(root_realm->hub_dir(), std::move(h1)) != ZX_OK) {
return zx::channel();
}
return h2;
}
void Realm::CreateNestedJob(
zx::channel host_directory,
fidl::InterfaceRequest<ApplicationEnvironment> environment,
fidl::InterfaceRequest<ApplicationEnvironmentController> controller_request,
fidl::StringPtr label) {
auto controller = std::make_unique<ApplicationEnvironmentControllerImpl>(
std::move(controller_request),
std::make_unique<Realm>(this, std::move(host_directory), label));
Realm* child = controller->realm();
child->AddBinding(std::move(environment));
// update hub
hub_.AddRealm(child);
children_.emplace(child, std::move(controller));
Realm* root_realm = this;
while (root_realm->parent() != nullptr) {
root_realm = root_realm->parent();
}
child->default_namespace_->services().ServeDirectory(
std::move(root_realm->svc_channel_server_));
}
void Realm::CreateApplication(
ApplicationLaunchInfo launch_info,
fidl::InterfaceRequest<ApplicationController> controller) {
if (launch_info.url.get().empty()) {
FXL_LOG(ERROR) << "Cannot create application because launch_info contains"
" an empty url";
return;
}
std::string canon_url = CanonicalizeURL(launch_info.url);
if (canon_url.empty()) {
FXL_LOG(ERROR) << "Cannot run " << launch_info.url
<< " because the url could not be canonicalized";
return;
}
launch_info.url = canon_url;
// launch_info is moved before LoadComponent() gets at its first argument.
fidl::StringPtr url = launch_info.url;
loader_->LoadComponent(
url, fxl::MakeCopyable([this, launch_info = std::move(launch_info),
controller = std::move(controller)](
ApplicationPackagePtr package) mutable {
fxl::RefPtr<Namespace> ns = default_namespace_;
if (launch_info.additional_services) {
ns = fxl::MakeRefCounted<Namespace>(
default_namespace_, this,
std::move(launch_info.additional_services));
}
if (package) {
if (package->data) {
std::string runner;
LaunchType type = Classify(package->data->vmo, &runner);
switch (type) {
case LaunchType::kProcess:
CreateApplicationWithProcess(
std::move(package), std::move(launch_info),
std::move(controller), std::move(ns));
break;
case LaunchType::kArchive:
CreateApplicationFromPackage(
std::move(package), std::move(launch_info),
std::move(controller), std::move(ns));
break;
}
} else if (package->directory) {
CreateApplicationFromPackage(std::move(package),
std::move(launch_info),
std::move(controller), std::move(ns));
}
}
}));
}
std::unique_ptr<ApplicationEnvironmentControllerImpl> 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);
children_.erase(it);
return controller;
}
std::unique_ptr<ApplicationControllerImpl> Realm::ExtractApplication(
ApplicationControllerImpl* controller) {
auto it = applications_.find(controller);
if (it == applications_.end()) {
return nullptr;
}
auto application = std::move(it->second);
// update hub
hub_.RemoveComponent(application.get());
applications_.erase(it);
return application;
}
void Realm::AddBinding(
fidl::InterfaceRequest<ApplicationEnvironment> environment) {
default_namespace_->AddBinding(std::move(environment));
}
void Realm::CreateApplicationWithProcess(
ApplicationPackagePtr package, ApplicationLaunchInfo launch_info,
fidl::InterfaceRequest<ApplicationController> controller,
fxl::RefPtr<Namespace> ns) {
zx::channel svc = ns->services().OpenAsDirectory();
if (!svc)
return;
NamespaceBuilder builder;
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.
// HACK(alhaad): We add deprecated default directories after this.
builder.AddFlatNamespace(std::move(launch_info.flat_namespace));
// TODO(abarth): Remove this call to AddDeprecatedDefaultDirectories once
// every application has a proper sandbox configuration.
builder.AddDeprecatedDefaultDirectories();
fsl::SizedVmo executable;
if (!fsl::SizedVmo::FromTransport(std::move(*package->data), &executable))
return;
const std::string args = GetArgsString(launch_info.arguments);
const std::string url = launch_info.url; // Keep a copy before moving it.
auto channels = BindDirectory(&launch_info);
zx::process process =
CreateProcess(job_for_child_, std::move(executable), url,
std::move(launch_info), zx::channel(), builder.Build());
if (process) {
auto application = std::make_unique<ApplicationControllerImpl>(
std::move(controller), this, nullptr, std::move(process), url,
std::move(args), GetLabelFromURL(url), std::move(ns),
ExportedDirType::kPublicDebugCtrlLayout,
std::move(channels.exported_dir), std::move(channels.client_request));
// update hub
hub_.AddComponent(application.get());
ApplicationControllerImpl* key = application.get();
applications_.emplace(key, std::move(application));
}
}
void Realm::CreateApplicationFromPackage(
ApplicationPackagePtr package, ApplicationLaunchInfo launch_info,
fidl::InterfaceRequest<ApplicationController> controller,
fxl::RefPtr<Namespace> ns) {
zx::channel svc = ns->services().OpenAsDirectory();
if (!svc)
return;
zx::channel pkg;
std::unique_ptr<archive::FileSystem> pkg_fs;
std::string sandbox_data;
std::string runtime_data;
ExportedDirType exported_dir_layout(ExportedDirType::kPublicDebugCtrlLayout);
fsl::SizedVmo app_data;
zx::channel loader_service;
if (package->data) {
pkg_fs =
std::make_unique<archive::FileSystem>(std::move(package->data->vmo));
pkg = pkg_fs->OpenAsDirectory();
pkg_fs->GetFileAsString(kSandboxPath, &sandbox_data);
if (!pkg_fs->GetFileAsString(kRuntimePath, &runtime_data))
app_data = pkg_fs->GetFileAsVMO(kAppPath);
exported_dir_layout = pkg_fs->IsFile(kLegacyFlatExportedDirPath)
? ExportedDirType::kLegacyFlatLayout
: ExportedDirType::kPublicDebugCtrlLayout;
} else if (package->directory) {
fxl::UniqueFD fd =
fsl::OpenChannelAsFileDescriptor(std::move(package->directory));
files::ReadFileToStringAt(fd.get(), kSandboxPath, &sandbox_data);
if (!files::ReadFileToStringAt(fd.get(), kRuntimePath, &runtime_data))
VmoFromFilenameAt(fd.get(), kAppPath, &app_data);
exported_dir_layout = files::IsFileAt(fd.get(), kLegacyFlatExportedDirPath)
? ExportedDirType::kLegacyFlatLayout
: ExportedDirType::kPublicDebugCtrlLayout;
// TODO(abarth): We shouldn't need to clone the channel here. Instead, we
// should be able to tear down the file descriptor in a way that gives us
// the channel back.
pkg = fsl::CloneChannelFromFileDescriptor(fd.get());
if (DynamicLibraryLoader::Start(std::move(fd), &loader_service) != ZX_OK)
return;
}
if (!pkg)
return;
// Note that |builder| is only used in the else block below. It is left here
// because we would like to use it everywhere once US-313 is fixed.
NamespaceBuilder builder;
builder.AddPackage(std::move(pkg));
builder.AddServices(std::move(svc));
if (!sandbox_data.empty()) {
SandboxMetadata sandbox;
if (!sandbox.Parse(sandbox_data)) {
FXL_LOG(ERROR) << "Failed to parse sandbox metadata for "
<< launch_info.url;
return;
}
// If an app has the "shell" feature, then we use the libraries from the
// system rather than from the package because programs spawned from the
// shell will need the system-provided libraries to run.
if (sandbox.HasFeature("shell"))
loader_service.reset();
builder.AddSandbox(sandbox, [this] { return OpenRootInfoDir(); });
}
// 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 (app_data) {
const std::string args = GetArgsString(launch_info.arguments);
const std::string url = launch_info.url; // Keep a copy before moving it.
auto channels = BindDirectory(&launch_info);
zx::process process = CreateProcess(
job_for_child_, std::move(app_data), kAppArv0, std::move(launch_info),
std::move(loader_service), builder.Build());
if (process) {
auto application = std::make_unique<ApplicationControllerImpl>(
std::move(controller), this, std::move(pkg_fs), std::move(process),
url, std::move(args), GetLabelFromURL(url), std::move(ns),
exported_dir_layout, std::move(channels.exported_dir),
std::move(channels.client_request));
// update hub
hub_.AddComponent(application.get());
ApplicationControllerImpl* key = application.get();
applications_.emplace(key, std::move(application));
}
} else {
RuntimeMetadata runtime;
if (!runtime.Parse(runtime_data)) {
FXL_LOG(ERROR) << "Failed to parse runtime metadata for "
<< launch_info.url;
return;
}
ApplicationPackage inner_package;
inner_package.resolved_url = package->resolved_url;
ApplicationStartupInfo startup_info;
startup_info.launch_info = std::move(launch_info);
startup_info.flat_namespace = builder.BuildForRunner();
auto* runner = GetOrCreateRunner(runtime.runner());
if (runner == nullptr) {
FXL_LOG(ERROR) << "Cannot create " << runner << " to run "
<< launch_info.url;
return;
}
runner->StartApplication(std::move(inner_package), std::move(startup_info),
std::move(pkg_fs), std::move(ns),
std::move(controller));
}
}
ApplicationRunnerHolder* Realm::GetOrCreateRunner(const std::string& runner) {
// We create the entry in |runners_| before calling ourselves
// recursively to detect cycles.
auto result = runners_.emplace(runner, nullptr);
if (result.second) {
Services runner_services;
ApplicationControllerPtr runner_controller;
ApplicationLaunchInfo runner_launch_info;
runner_launch_info.url = runner;
runner_launch_info.directory_request = runner_services.NewRequest();
CreateApplication(std::move(runner_launch_info),
runner_controller.NewRequest());
runner_controller.set_error_handler(
[this, runner] { runners_.erase(runner); });
result.first->second = std::make_unique<ApplicationRunnerHolder>(
std::move(runner_services), std::move(runner_controller));
} else if (!result.first->second) {
// There was a cycle in the runner graph.
FXL_LOG(ERROR) << "Detected a cycle in the runner graph for " << runner
<< ".";
return nullptr;
}
return result.first->second.get();
}
zx_status_t Realm::BindSvc(zx::channel channel) {
Realm* root_realm = this;
while (root_realm->parent() != nullptr) {
root_realm = root_realm->parent();
}
return fdio_service_clone_to(root_realm->svc_channel_client_.get(),
channel.release());
}
} // namespace component