blob: b4bc63c64da2709d8740631d070edab4f4cbb50f [file] [log] [blame]
// 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::{self, ServerEnd};
use fidl_fuchsia_component as fcomponent;
use fidl_fuchsia_component_runner::{
self as fcrunner, ComponentControllerMarker, ComponentStartInfo,
};
use fidl_fuchsia_io as fio;
use fidl_fuchsia_starnix_developer as fstardev;
use fidl_fuchsia_sys2 as fsys;
use fuchsia_async as fasync;
use fuchsia_component::client as fclient;
use fuchsia_component::server::ServiceFs;
use fuchsia_zircon::{
self as zx, sys::zx_exception_info_t, sys::ZX_EXCEPTION_STATE_HANDLED,
sys::ZX_EXCEPTION_STATE_TRY_NEXT, sys::ZX_EXCP_POLICY_CODE_BAD_SYSCALL,
sys::ZX_EXCP_POLICY_ERROR, AsHandleRef, Task as zxTask,
};
use futures::{StreamExt, TryStreamExt};
use log::{error, info};
use rand::Rng;
use std::ffi::CString;
use std::mem;
use std::sync::Arc;
mod auth;
mod collections;
mod fs;
mod loader;
mod logging;
mod mm;
mod syscall_table;
mod syscalls;
mod task;
mod uapi;
mod user_address;
use crate::fs::FileSystem;
use crate::loader::*;
use crate::syscall_table::dispatch_syscall;
use crate::syscalls::SyscallContext;
use crate::task::*;
use crate::uapi::SyscallDecl;
use crate::uapi::SyscallResult;
// TODO: Should we move this code into fuchsia_zircon? It seems like part of a better abstraction
// for exception channels.
fn as_exception_info(buffer: &zx::MessageBuf) -> zx_exception_info_t {
let mut tmp = [0; mem::size_of::<zx_exception_info_t>()];
tmp.clone_from_slice(buffer.bytes());
unsafe { mem::transmute(tmp) }
}
fn read_channel_sync(chan: &zx::Channel, buf: &mut zx::MessageBuf) -> Result<(), zx::Status> {
let res = chan.read(buf);
if let Err(zx::Status::SHOULD_WAIT) = res {
chan.wait_handle(
zx::Signals::CHANNEL_READABLE | zx::Signals::CHANNEL_PEER_CLOSED,
zx::Time::INFINITE,
)?;
chan.read(buf)
} else {
res
}
}
/// Runs the process associated with `process_context`.
///
/// The process in `process_context.handle` is expected to already have been started. This function
/// listens to the exception channel for the process (`exceptions`) and handles each exception
/// by:
/// - verifying that the exception represents a `ZX_EXCP_POLICY_CODE_BAD_SYSCALL`
/// - reading the thread's registers
/// - executing the appropriate syscall
/// - setting the thread's registers to their post-syscall values
/// - setting the exception state to `ZX_EXCEPTION_STATE_HANDLED`
///
/// Once this function has completed, the process' exit code (if one is available) can be read from
/// `process_context.exit_code`.
fn run_task(task_owner: TaskOwner, exceptions: zx::Channel) -> Result<i32, Error> {
let task = &task_owner.task;
let mut buffer = zx::MessageBuf::new();
while read_channel_sync(&exceptions, &mut buffer).is_ok() {
let info = as_exception_info(&buffer);
assert!(buffer.n_handles() == 1);
let exception = zx::Exception::from(buffer.take_handle(0).unwrap());
if info.type_ != ZX_EXCP_POLICY_ERROR {
info!("exception type: 0x{:x}", info.type_);
exception.set_exception_state(&ZX_EXCEPTION_STATE_TRY_NEXT)?;
continue;
}
let thread = exception.get_thread()?;
assert!(
thread.get_koid() == task.thread.get_koid(),
"Exception thread did not match task thread."
);
let report = thread.get_exception_report()?;
if report.context.synth_code != ZX_EXCP_POLICY_CODE_BAD_SYSCALL {
info!("exception synth_code: {}", report.context.synth_code);
exception.set_exception_state(&ZX_EXCEPTION_STATE_TRY_NEXT)?;
continue;
}
let syscall_number = report.context.synth_data as u64;
let mut ctx = SyscallContext { task, registers: thread.read_state_general_regs()? };
let regs = &ctx.registers;
let args = (regs.rdi, regs.rsi, regs.rdx, regs.r10, regs.r8, regs.r9);
strace!(
"{}({:#x}, {:#x}, {:#x}, {:#x}, {:#x}, {:#x})",
SyscallDecl::from_number(syscall_number).name,
args.0,
args.1,
args.2,
args.3,
args.4,
args.5
);
match dispatch_syscall(&mut ctx, syscall_number, args) {
Ok(SyscallResult::Exit(error_code)) => {
strace!("-> exit {:#x}", error_code);
// TODO: Set the error_code on the Zircon process object. Currently missing a way
// to do this in Zircon. Might be easier in the new execution model.
return Ok(error_code);
}
Ok(SyscallResult::Success(return_value)) => {
strace!("-> {:#x}", return_value);
ctx.registers.rax = return_value;
}
Err(errno) => {
strace!("!-> {}", errno);
ctx.registers.rax = (-errno.value()) as u64;
}
}
thread.write_state_general_regs(ctx.registers)?;
exception.set_exception_state(&ZX_EXCEPTION_STATE_HANDLED)?;
}
Ok(0)
}
async fn start_component(
kernel: Arc<Kernel>,
start_info: ComponentStartInfo,
controller: ServerEnd<ComponentControllerMarker>,
) -> Result<(), Error> {
info!(
"start_component: {}",
start_info.resolved_url.clone().unwrap_or("<unknown>".to_string())
);
let root_path = runner::get_program_string(&start_info, "root")
.ok_or_else(|| anyhow!("No root in component manifest"))?
.to_owned();
let binary_path = runner::get_program_binary(&start_info)?;
let ns = start_info.ns.ok_or_else(|| anyhow!("Missing namespace"))?;
let pkg = fio::DirectorySynchronousProxy::new(
ns.into_iter()
.find(|entry| entry.path == Some("/pkg".to_string()))
.ok_or_else(|| anyhow!("Missing /pkg entry in namespace"))?
.directory
.ok_or_else(|| anyhow!("Missing directory handlee in pkg namespace entry"))?
.into_channel(),
);
let root = syncio::directory_open_directory_async(
&pkg,
&root_path,
fio::OPEN_RIGHT_READABLE | fio::OPEN_RIGHT_EXECUTABLE,
)
.map_err(|e| anyhow!("Failed to open root: {}", e))?;
let executable_vmo = syncio::directory_open_vmo(
&root,
&binary_path,
fio::VMO_FLAG_READ | fio::VMO_FLAG_EXEC,
zx::Time::INFINITE,
)
.map_err(|e| anyhow!("Failed to load executable: {}", e))?;
let fs = Arc::new(FileSystem::new(root));
let params = ProcessParameters {
name: CString::new(binary_path.clone())?,
argv: vec![CString::new(binary_path.clone())?],
environ: vec![],
};
std::thread::spawn(move || {
let err = (|| -> Result<(), Error> {
let task_owner = load_executable(&kernel, executable_vmo, &params, fs)?;
let exceptions = task_owner.task.thread.create_exception_channel()?;
let exit_code = run_task(task_owner, exceptions)?;
// TODO(fxb/74803): Using the component controller's epitaph may not be the best way to
// communicate the exit code. The component manager could interpret certain epitaphs as starnix
// being unstable, and chose to terminate starnix as a result.
// Errors when closing the controller with an epitaph are disregarded, since there are
// legitimate reasons for this to fail (like the client having closed the channel).
let _ = match exit_code {
0 => controller.close_with_epitaph(zx::Status::OK),
_ => controller.close_with_epitaph(zx::Status::from_raw(
fcomponent::Error::InstanceDied.into_primitive() as i32,
)),
};
Ok(())
})();
err.unwrap();
});
Ok(())
}
async fn start_runner(
kernel: Arc<Kernel>,
mut request_stream: fcrunner::ComponentRunnerRequestStream,
) -> Result<(), Error> {
while let Some(event) = request_stream.try_next().await? {
match event {
fcrunner::ComponentRunnerRequest::Start { start_info, controller, .. } => {
let kernel = kernel.clone();
fasync::Task::local(async move {
if let Err(e) = start_component(kernel, start_info, controller).await {
error!("failed to start component: {}", e);
}
})
.detach();
}
}
}
Ok(())
}
async fn start(url: String) -> Result<(), Error> {
// TODO(fxbug.dev/74511): The amount of setup required here is a bit lengthy. Ideally,
// fuchsia-component would provide native 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 = fsys::CollectionRef { name: COLLECTION.into() };
let id: u64 = rand::thread_rng().gen();
let child_name = format!("starnix-{}", id);
let child_decl = fsys::ChildDecl {
name: Some(child_name.clone()),
url: Some(url),
startup: Some(fsys::StartupMode::Lazy),
environment: None,
..fsys::ChildDecl::EMPTY
};
let () = realm
.create_child(&mut collection_ref, child_decl)
.await?
.map_err(|e| format_err!("failed to create child: {:?}", e))?;
let mut child_ref =
fsys::ChildRef { name: child_name.clone(), collection: Some(COLLECTION.into()) };
let (_, server) = endpoints::create_proxy::<fidl_fuchsia_io::DirectoryMarker>()?;
let () = realm
.bind_child(&mut child_ref, server)
.await?
.map_err(|e| format_err!("failed to bind to child: {:?}", e))?;
// We currently don't track the instance so we will never terminate it. Eventually, we'll keep
// track of all the running instances and be able to stop them.
Ok(())
}
async fn start_manager(mut request_stream: fstardev::ManagerRequestStream) -> Result<(), Error> {
while let Some(event) = request_stream.try_next().await? {
match event {
fstardev::ManagerRequest::Start { url, responder } => {
if let Err(e) = start(url).await {
error!("failed to start component: {}", e);
}
responder.send()?;
}
fstardev::ManagerRequest::StartShell { .. } => {
not_implemented!("StartShell not yet implemented.")
}
}
}
Ok(())
}
#[fasync::run_singlethreaded]
async fn main() -> Result<(), Error> {
fuchsia_syslog::init_with_tags(&["starnix"]).expect("failed to initialize logger");
info!("main");
// The root kernel object for this instance of Starnix.
let kernel = Kernel::new(&CString::new("kernel")?)?;
let mut fs = ServiceFs::new_local();
fs.dir("svc").add_fidl_service(move |stream| {
let kernel = kernel.clone();
fasync::Task::local(async move {
start_runner(kernel, stream).await.expect("failed to start runner.")
})
.detach();
});
fs.dir("svc").add_fidl_service(move |stream| {
fasync::Task::local(async move {
start_manager(stream).await.expect("failed to start manager.")
})
.detach();
});
fs.take_and_serve_directory_handle()?;
fs.collect::<()>().await;
Ok(())
}