blob: db87df577bfb32438a79ffa239bf93371acfc539 [file]
// Copyright 2021 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 anyhow::{anyhow, format_err, Context, Error};
use fidl::endpoints::ServerEnd;
use fidl_fuchsia_component as fcomponent;
use fidl_fuchsia_component_decl as fdecl;
use fidl_fuchsia_process as fprocess;
use fidl_fuchsia_starnix_developer as fstardev;
use fidl_fuchsia_starnix_galaxy as fstargalaxy;
use fuchsia_async as fasync;
use fuchsia_component::{client as fclient, server::ServiceFs};
use fuchsia_runtime::{HandleInfo, HandleType};
use fuchsia_zircon::HandleBased;
use futures::{StreamExt, TryStreamExt};
use rand::Rng;
#[fuchsia::main(logging_tags = ["starnix_manager"])]
async fn main() -> Result<(), Error> {
let mut fs = ServiceFs::new_local();
fs.dir("svc").add_fidl_service(move |stream| {
fasync::Task::local(async move {
serve_starnix_manager(stream).await.expect("failed to start manager.")
})
.detach();
});
fs.take_and_serve_directory_handle()?;
fs.collect::<()>().await;
Ok(())
}
pub async fn serve_starnix_manager(
mut request_stream: fstardev::ManagerRequestStream,
) -> Result<(), Error> {
while let Some(event) = request_stream.try_next().await? {
match event {
fstardev::ManagerRequest::Start { url, responder } => {
let args = fcomponent::CreateChildArgs {
numbered_handles: None,
..fcomponent::CreateChildArgs::EMPTY
};
if let Err(e) = create_child_component(url, args).await {
tracing::error!("failed to create child component: {}", e);
}
responder.send()?;
}
fstardev::ManagerRequest::StartShell { params, controller, .. } => {
start_shell(params, controller).await?;
}
fstardev::ManagerRequest::VsockConnect { galaxy, port, bridge_socket, .. } => {
connect_to_vsock(port, bridge_socket, &galaxy).unwrap_or_else(|e| {
tracing::error!("failed to connect to vsock {:?}", e);
});
}
}
}
Ok(())
}
async fn start_shell(
params: fstardev::ShellParams,
controller: ServerEnd<fstardev::ShellControllerMarker>,
) -> Result<(), Error> {
let controller_handle_info = fprocess::HandleInfo {
handle: controller.into_channel().into_handle(),
id: HandleInfo::new(HandleType::User0, 0).as_raw(),
};
let numbered_handles = vec![
handle_info_from_socket(params.standard_in, 0)?,
handle_info_from_socket(params.standard_out, 1)?,
handle_info_from_socket(params.standard_err, 2)?,
controller_handle_info,
];
let args = fcomponent::CreateChildArgs {
numbered_handles: Some(numbered_handles),
..fcomponent::CreateChildArgs::EMPTY
};
let url = params.url.ok_or(anyhow!("No shell URL specified"))?;
create_child_component(url, args).await
}
/// Connects `bridge_socket` to the vsocket at `port` in the specified galaxy.
///
/// Returns an error if the FIDL connection to the galaxy failed.
fn connect_to_vsock(port: u32, bridge_socket: fidl::Socket, galaxy: &str) -> Result<(), Error> {
let service_prefix = "/".to_string() + galaxy;
let galaxy = fclient::connect_to_protocol_at::<fstargalaxy::ControllerMarker>(&service_prefix)?;
galaxy.vsock_connect(port, bridge_socket).context("Failed to call vsock connect on galaxy")
}
/// Creates a `HandleInfo` from the provided socket and file descriptor.
///
/// The file descriptor is encoded as a `PA_HND(PA_FD, <file_descriptor>)` before being stored in
/// the `HandleInfo`.
///
/// Returns an error if `socket` is `None`.
pub fn handle_info_from_socket(
socket: Option<fidl::Socket>,
file_descriptor: u16,
) -> Result<fprocess::HandleInfo, Error> {
if let Some(socket) = socket {
let info = HandleInfo::new(HandleType::FileDescriptor, file_descriptor);
Ok(fprocess::HandleInfo { handle: socket.into_handle(), id: info.as_raw() })
} else {
Err(anyhow!("Failed to create HandleInfo for {}", file_descriptor))
}
}
/// Creates a new child component in the `playground` collection.
///
/// # Parameters
/// - `url`: The URL of the component to create.
/// - `args`: The `CreateChildArgs` that are passed to the component manager.
pub async fn create_child_component(
url: String,
args: fcomponent::CreateChildArgs,
) -> Result<(), Error> {
// TODO(fxbug.dev/74511): The amount of setup required here is a bit lengthy. Ideally,
// fuchsia-component would provide language-specific bindings for the Realm API that could
// reduce this logic to a few lines.
const COLLECTION: &str = "playground";
let realm = fclient::realm().context("failed to connect to Realm service")?;
let mut collection_ref = fdecl::CollectionRef { name: COLLECTION.into() };
let id: u64 = rand::thread_rng().gen();
let child_name = format!("starnix-{}", id);
let child_decl = fdecl::Child {
name: Some(child_name.clone()),
url: Some(url),
startup: Some(fdecl::StartupMode::Lazy),
environment: None,
..fdecl::Child::EMPTY
};
let () = realm
.create_child(&mut collection_ref, child_decl, args)
.await?
.map_err(|e| format_err!("failed to create child: {:?}", e))?;
// The component is run in a `SingleRun` collection instance, and will be automatically
// deleted when it exits.
Ok(())
}