blob: fddb57253233d592a1fd1d4341dddda38ce98096 [file] [log] [blame]
// Copyright 2022 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.
use fidl::endpoints::DiscoverableProtocolMarker;
use fidl::endpoints::Proxy;
use fidl_fuchsia_component_runner as fcrunner;
use fidl_fuchsia_dash::LauncherError;
use fidl_fuchsia_io as fio;
use fidl_fuchsia_process as fproc;
use fuchsia_async as fasync;
use fuchsia_component::client::connect_channel_to_protocol;
use fuchsia_component::server::ServiceFs;
use futures::StreamExt;
use tracing::warn;
use vfs::{
directory::{entry_container::Directory, helper::DirectlyMutable},
execution_scope::ExecutionScope,
path::Path,
service::endpoint,
};
pub fn package_layout(
svc_dir: fio::DirectoryProxy,
target_dir: fio::DirectoryProxy,
) -> Vec<fproc::NameInfo> {
vec![to_name_info("/svc", svc_dir), to_name_info("/pkg", target_dir)]
}
/// Returns directory handles + paths for a nested layout using the given directories.
/// In this layout, all instance directories are nested as subdirectories of the root.
/// e.g - namespace is under `/ns`, outgoing directory is under `/out`, etc.
pub fn nest_all_instance_dirs(
ns_entries: Vec<fcrunner::ComponentNamespaceEntry>,
exposed_dir: fio::DirectoryProxy,
svc_dir: fio::DirectoryProxy,
out_dir: Option<fio::DirectoryProxy>,
runtime_dir: Option<fio::DirectoryProxy>,
) -> Vec<fproc::NameInfo> {
let mut name_infos = vec![];
name_infos.push(to_name_info("/exposed", exposed_dir));
name_infos.push(to_name_info("/svc", svc_dir));
for entry in ns_entries {
let path = format!("/ns{}", entry.path.unwrap());
let directory = entry.directory.unwrap();
name_infos.push(fproc::NameInfo { path, directory });
}
if let Some(dir) = out_dir {
name_infos.push(to_name_info("/out", dir));
}
if let Some(dir) = runtime_dir {
name_infos.push(to_name_info("/runtime", dir));
}
name_infos
}
pub fn add_tools_to_name_infos(
tools: Option<fio::DirectoryProxy>,
name_infos: &mut Vec<fproc::NameInfo>,
) {
if let Some(dir) = tools {
name_infos.push(to_name_info("/.dash/tools", dir));
}
}
/// Returns directory handles + paths for a namespace layout using the given directories.
/// In this layout, the instance namespace is the root. This is a layout that works
/// well for tools and closely resembles what the component instance would see if it queried its
/// own namespace.
///
/// To make the shell work correctly, we need to inject the following into the layout:
/// * fuchsia.process.Resolver and fuchsia.process.Launcher into `/svc`
/// * tools packages, if given, into `/.dash/tools`
///
/// Also returns the corresponding PATH envvar that must be set for the dash shell.
pub async fn instance_namespace_is_root(
ns_entries: Vec<fcrunner::ComponentNamespaceEntry>,
) -> Vec<fproc::NameInfo> {
let mut name_infos = vec![];
for entry in ns_entries {
let path = entry.path.unwrap();
let directory = entry.directory.unwrap();
if path == "/svc" {
let svc_dir = directory.into_proxy().unwrap();
let svc_dir = inject_process_launcher_and_resolver(svc_dir).await;
name_infos.push(to_name_info(&path, svc_dir));
} else {
name_infos.push(fproc::NameInfo { path, directory });
}
}
name_infos
}
/// Serves a VFS that contains `fuchsia.process.Launcher` and `fuchsia.process.Resolver`. This is
/// used by the Nested filesystem layout.
pub fn serve_process_launcher_and_resolver_svc_dir() -> Result<fio::DirectoryProxy, LauncherError> {
// Serve a directory that only provides fuchsia.process.Launcher to dash.
let (svc_dir, server_end) = fidl::endpoints::create_proxy::<fio::DirectoryMarker>()
.map_err(|_| LauncherError::Internal)?;
let mut fs = ServiceFs::new();
fs.add_proxy_service::<fproc::LauncherMarker, ()>();
fs.add_proxy_service::<fproc::ResolverMarker, ()>();
fs.serve_connection(server_end).map_err(|_| LauncherError::ProcessResolver)?;
fasync::Task::spawn(async move {
fs.collect::<()>().await;
})
.detach();
Ok(svc_dir)
}
/// Gets the list of all protocols available in the given svc directory and serves a new VFS
/// that injects `fuchsia.process.Launcher` from the launcher namespace and forwards the calls
/// for the other protocols. Also adds the `fuchsia.process.Resolver` for tool trampolines.
async fn inject_process_launcher_and_resolver(svc_dir: fio::DirectoryProxy) -> fio::DirectoryProxy {
let vfs = vfs::directory::immutable::simple();
let entries = fuchsia_fs::directory::readdir(&svc_dir).await.unwrap();
// Create forwarding entries for namespace protocols
for entry in entries {
let svc_dir = std::clone::Clone::clone(&svc_dir);
let protocol_name = entry.name.clone();
vfs.add_entry(
protocol_name.clone(),
endpoint(move |_, channel| {
let server_end = channel.into_zx_channel().into();
svc_dir
.open(
fio::OpenFlags::NOT_DIRECTORY,
fio::ModeType::empty(),
&protocol_name,
server_end,
)
.unwrap();
}),
)
.unwrap();
}
// Add process launcher.
if let Err(err) = vfs.add_entry(
fproc::LauncherMarker::PROTOCOL_NAME,
endpoint(|_, channel| {
connect_channel_to_protocol::<fproc::LauncherMarker>(channel.into_zx_channel())
.unwrap();
}),
) {
warn!(?err, "Could not inject fuchsia.process.Launcher into filesystem layout. Ignoring.");
}
// Add process resolver.
if let Err(err) = vfs.add_entry(
fproc::ResolverMarker::PROTOCOL_NAME,
endpoint(|_, channel| {
connect_channel_to_protocol::<fproc::ResolverMarker>(channel.into_zx_channel())
.unwrap();
}),
) {
warn!(?err, "Could not inject fuchsia.process.Resolver into filesystem layout. Ignoring.");
}
let (svc_dir, server_end) = fidl::endpoints::create_proxy::<fio::DirectoryMarker>().unwrap();
let server_end = server_end.into_channel().into();
vfs.open(
ExecutionScope::new(),
fio::OpenFlags::RIGHT_READABLE | fio::OpenFlags::RIGHT_WRITABLE,
Path::dot(),
server_end,
);
svc_dir
}
fn to_name_info(path: &str, directory: fio::DirectoryProxy) -> fproc::NameInfo {
let directory = directory.into_channel().unwrap().into_zx_channel().into();
fproc::NameInfo { path: path.to_string(), directory }
}
#[cfg(test)]
mod tests {
use super::*;
use std::path::PathBuf;
use tempfile::TempDir;
fn create_temp_dir(file_name: &str) -> fio::DirectoryProxy {
// Create a temp directory and put a file with name `file_name` inside it.
let temp_dir = TempDir::new().unwrap();
let temp_dir_path = temp_dir.into_path();
let file_path = temp_dir_path.join(file_name);
std::fs::write(&file_path, "Hippos Rule!").unwrap();
let temp_dir_path = temp_dir_path.display().to_string();
fuchsia_fs::directory::open_in_namespace(&temp_dir_path, fio::OpenFlags::RIGHT_READABLE)
.unwrap()
}
fn create_ns_entries() -> Vec<fcrunner::ComponentNamespaceEntry> {
let ns_subdir = create_temp_dir("ns_subdir");
let ns_subdir = ns_subdir.into_channel().unwrap().into_zx_channel().into();
vec![fcrunner::ComponentNamespaceEntry {
path: Some("/ns_subdir".to_string()),
directory: Some(ns_subdir),
..Default::default()
}]
}
#[fuchsia::test]
async fn nest_all_instance_dirs_started_with_tools() {
let exposed_dir = create_temp_dir("exposed");
let out_dir = create_temp_dir("out");
let svc_dir = create_temp_dir("svc");
let runtime_dir = create_temp_dir("runtime");
let ns_entries = create_ns_entries();
let ns = nest_all_instance_dirs(
ns_entries,
exposed_dir,
svc_dir,
Some(out_dir),
Some(runtime_dir),
);
assert_eq!(ns.len(), 5);
let mut paths: Vec<String> = ns.iter().map(|n| n.path.clone()).collect();
paths.sort();
assert_eq!(paths, vec!["/exposed", "/ns/ns_subdir", "/out", "/runtime", "/svc"]);
// Make sure that the correct directories were mapped to the correct paths.
for entry in ns {
let dir = entry.directory.into_proxy().unwrap();
let entries = fuchsia_fs::directory::readdir(&dir).await.unwrap();
// These directories must contain a file with the same name
let path = PathBuf::from(entry.path);
let expected_file_name = path.file_name().unwrap().to_str().unwrap().to_string();
assert_eq!(
entries,
vec![fuchsia_fs::directory::DirEntry {
name: expected_file_name,
kind: fuchsia_fs::directory::DirentKind::File
}]
);
}
}
#[fuchsia::test]
async fn nest_all_instance_dirs_started() {
let exposed_dir = create_temp_dir("exposed");
let out_dir = create_temp_dir("out");
let svc_dir = create_temp_dir("svc");
let runtime_dir = create_temp_dir("runtime");
let ns_entries = create_ns_entries();
let ns = nest_all_instance_dirs(
ns_entries,
exposed_dir,
svc_dir,
Some(out_dir),
Some(runtime_dir),
);
assert_eq!(ns.len(), 5);
let mut paths: Vec<String> = ns.iter().map(|n| n.path.clone()).collect();
paths.sort();
assert_eq!(paths, vec!["/exposed", "/ns/ns_subdir", "/out", "/runtime", "/svc"]);
// Make sure that the correct directories were mapped to the correct paths.
for entry in ns {
let dir = entry.directory.into_proxy().unwrap();
let entries = fuchsia_fs::directory::readdir(&dir).await.unwrap();
// These directories must contain a file with the same name
let path = PathBuf::from(entry.path);
let expected_file_name = path.file_name().unwrap().to_str().unwrap().to_string();
assert_eq!(
entries,
vec![fuchsia_fs::directory::DirEntry {
name: expected_file_name,
kind: fuchsia_fs::directory::DirentKind::File
}]
);
}
}
#[fuchsia::test]
async fn nest_all_instance_dirs_resolved() {
let exposed_dir = create_temp_dir("exposed");
let svc_dir = create_temp_dir("svc");
let ns_entries = create_ns_entries();
let ns = nest_all_instance_dirs(ns_entries, exposed_dir, svc_dir, None, None);
assert_eq!(ns.len(), 3);
let mut paths: Vec<String> = ns.iter().map(|n| n.path.clone()).collect();
paths.sort();
assert_eq!(paths, vec!["/exposed", "/ns/ns_subdir", "/svc"]);
// Make sure that the correct directories were mapped to the correct paths.
for entry in ns {
let dir = entry.directory.into_proxy().unwrap();
let entries = fuchsia_fs::directory::readdir(&dir).await.unwrap();
// These directories must contain a file with the same name
let path = PathBuf::from(entry.path);
let expected_file_name = path.file_name().unwrap().to_str().unwrap().to_string();
assert_eq!(
entries,
vec![fuchsia_fs::directory::DirEntry {
name: expected_file_name,
kind: fuchsia_fs::directory::DirentKind::File
}]
);
}
}
#[fuchsia::test]
async fn instance_namespace_is_root_resolved() {
let ns_entries = create_ns_entries();
let ns = instance_namespace_is_root(ns_entries).await;
let mut paths: Vec<String> = ns.iter().map(|n| n.path.clone()).collect();
paths.sort();
assert_eq!(paths, vec!["/ns_subdir"]);
// Make sure that the correct directories were mapped to the correct paths.
for entry in ns {
let dir = entry.directory.into_proxy().unwrap();
let entries = fuchsia_fs::directory::readdir(&dir).await.unwrap();
// These directories must contain a file with the same name
let path = PathBuf::from(entry.path);
let expected_file_name = path.file_name().unwrap().to_str().unwrap().to_string();
assert_eq!(
entries,
vec![fuchsia_fs::directory::DirEntry {
name: expected_file_name,
kind: fuchsia_fs::directory::DirentKind::File
}]
);
}
}
}