blob: f3d5baa806194bd2c1e73089e1a500724d63f1aa [file] [log] [blame] [edit]
// 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.
#![recursion_limit = "256"]
#![allow(clippy::too_many_arguments)]
// TODO(https://fxbug.dev/42073005): Remove this allow once the lint is fixed.
#![allow(unknown_lints, clippy::extra_unused_type_parameters)]
// Avoid unused crate warnings on non-test/non-debug builds because this needs to be an
// unconditional dependency for rustdoc generation.
use {extended_pstate as _, tracing_mutex as _};
use anyhow::{Context as _, Error};
use async_lock::OnceCell;
use fuchsia_component::server::ServiceFs;
use fuchsia_inspect::health::Reporter;
use futures::{StreamExt, TryStreamExt};
use starnix_core::mm::{init_usercopy, zxio_maybe_faultable_copy_impl};
use starnix_kernel_runner::{
Container, ContainerServiceConfig, create_component_from_stream, serve_component_runner,
serve_container_controller, serve_memory_attribution_provider_elfkernel,
};
use starnix_kernel_structured_config::Config as KernelStructuredConfig;
use starnix_logging::{
CATEGORY_STARNIX, NAME_START_KERNEL, log_debug, log_error, log_warn, trace_instant,
};
use std::rc::Rc;
use {
fidl_fuchsia_component_runner as frunner, fidl_fuchsia_memory_attribution as fattribution,
fidl_fuchsia_process_lifecycle as flifecycle, fidl_fuchsia_starnix_container as fstarcontainer,
fuchsia_async as fasync, fuchsia_runtime as fruntime,
};
/// Overrides the `zxio_maybe_faultable_copy` weak symbol found in zxio.
#[unsafe(no_mangle)]
extern "C" fn zxio_maybe_faultable_copy(
dest: *mut u8,
src: *const u8,
count: usize,
ret_dest: bool,
) -> bool {
// SAFETY: we know that we are either copying from or to a buffer that
// zxio (and thus Starnix) owns per `zxio_maybe_faultable_copy`'s
// documentation.
unsafe { zxio_maybe_faultable_copy_impl(dest, src, count, ret_dest) }
}
/// Overrides the `zxio_fault_catching_disabled` weak symbol found in zxio.
#[unsafe(no_mangle)]
extern "C" fn zxio_fault_catching_disabled() -> bool {
false
}
fn maybe_serve_lifecycle(container: Rc<OnceCell<Container>>) -> Option<fasync::Task<()>> {
if let Some(lifecycle) =
fruntime::take_startup_handle(fruntime::HandleInfo::new(fruntime::HandleType::Lifecycle, 0))
{
Some(fasync::Task::local(async move {
let mut stream =
fidl::endpoints::ServerEnd::<flifecycle::LifecycleMarker>::new(lifecycle.into())
.into_stream();
if let Ok(Some(request)) = stream.try_next().await {
match request {
flifecycle::LifecycleRequest::Stop { .. } => {
if let Some(container) = container.get() {
container.kernel.shut_down();
} else {
log_warn!("Stopping kernel process without a running container.");
std::process::exit(0);
}
}
}
}
}))
} else {
log_warn!("No lifecycle channel received from ELF runner.");
None
}
}
enum KernelServices {
/// This service lets clients start a single container using this kernel.
///
/// The `starnix_kernel` is capable of running a single container, which can be started using
/// this protocol. Attempts to use this protocol a second time will fail.
///
/// This service uses the `ComponentRunner` protocol but the service is exposed using the name
/// `fuchsia.starnix.container.Runner` to reduce confusion with the instance of the
/// `ComponentRunner` protocol that runs components inside the container.
ContainerRunner(frunner::ComponentRunnerRequestStream),
/// This service lets clients run components inside the container being run by this kernel.
///
/// This service will wait to process any requests until the kernel starts a container.
///
/// This service is also exposed via the container itself.
ComponentRunner(frunner::ComponentRunnerRequestStream),
/// This service lets clients control the container being run by this kernel.
///
/// This service will wait to process any requests until the kernel starts a container.
///
/// This service is also exposed via the container itself.
ContainerController(fstarcontainer::ControllerRequestStream),
/// This service lets clients read which memory resources are used to run the
/// various starnix programs in the container.
///
/// It provides a finer grained attribution than possible with Zircon process level
/// tooling, because all starnix processes in a container share the same handle table.
/// This protocol lets the kernel report exactly which VMOs it used to run a starnix
/// program, out of VMOs in the shared handle table.
///
/// The starnix runner connects to this protocol to report memory attribution
/// information for each container it runs.
MemoryAttributionProvider(fattribution::ProviderRequestStream),
}
#[fuchsia::main(
// Don't add any statically declared tags to reduce right-ward drift in log output. In practice
// all logs get tagged with task info that makes it clear from context the log comes from
// Starnix.
logging_tags = [],
// LINT.IfChange(starnix_panic_tefmo)
logging_panic_prefix="\n\n\n\nSTARNIX KERNEL PANIC\n\n\n\n",
// LINT.ThenChange(//tools/testing/tefmocheck/string_in_log_check.go:starnix_panic_tefmo)
)]
async fn main() -> Result<(), Error> {
// Make sure that if this process panics in normal mode that the whole kernel's job is killed.
fruntime::job_default()
.set_critical(zx::JobCriticalOptions::RETCODE_NONZERO, &fruntime::process_self())
.context("ensuring main process panics kill whole kernel")?;
let kernel_structured_config = KernelStructuredConfig::take_from_startup_handle();
let KernelStructuredConfig { extra_features: kernel_extra_features } = kernel_structured_config;
let _inspect_server_task = inspect_runtime::publish(
fuchsia_inspect::component::init_inspector_with_size(1_000_000),
inspect_runtime::PublishOptions::default(),
);
fuchsia_inspect::component::serve_inspect_stats();
let mut health = fuchsia_inspect::component::health();
health.set_starting_up();
fuchsia_trace_provider::trace_provider_create_with_fdio();
fuchsia_trace_provider::trace_provider_wait_for_init();
trace_instant!(CATEGORY_STARNIX, NAME_START_KERNEL, fuchsia_trace::Scope::Thread);
starnix_kernel_runner::initialize();
let container = Rc::new(OnceCell::<Container>::new());
let _lifecycle_task = maybe_serve_lifecycle(container.clone());
let mut fs = ServiceFs::new_local();
fs.dir("svc")
.add_fidl_service_at("fuchsia.starnix.container.Runner", KernelServices::ContainerRunner)
.add_fidl_service(KernelServices::ComponentRunner)
.add_fidl_service(KernelServices::ContainerController)
.add_fidl_service(KernelServices::MemoryAttributionProvider);
let inspector = fuchsia_inspect::component::inspector();
#[cfg(target_arch = "x86_64")]
{
inspector.root().record_string(
"x86_64_extended_pstate_strategy",
format!("{:?}", *extended_pstate::x86_64::PREFERRED_STRATEGY),
);
}
inspector.root().record_lazy_child("not_found", starnix_logging::not_found_lazy_node_callback);
inspector.root().record_lazy_child("stubs", starnix_logging::track_stub_lazy_node_callback);
starnix_logging::register_stub_context_callback();
log_debug!("Serving kernel services on outgoing directory handle.");
fs.take_and_serve_directory_handle()?;
health.set_ok();
// We call this early during Starnix boot to make sure the usercopy utilities
// are ready for use before any restricted-mode/Linux processes are created.
init_usercopy();
while let Some(request) = fs.next().await {
match request {
KernelServices::ContainerRunner(stream) => {
let container = container.clone();
let kernel_extra_features = kernel_extra_features.clone();
fuchsia_async::Task::local(async move {
let mut config: Option<ContainerServiceConfig> = None;
let container = container
.get_or_try_init(|| async {
create_component_from_stream(stream, kernel_extra_features).await.map(
|(container, new_config)| {
config = Some(new_config);
container
},
)
})
.await
.expect("failed to start container");
if let Some(config) = config {
container
.serve(config)
.await
.expect("failed to serve the expected services from the container");
} else {
log_error!("No config provided for container, not running it.");
}
})
.detach();
}
KernelServices::ComponentRunner(stream) => {
let container = container.clone();
fuchsia_async::Task::local(async move {
serve_component_runner(stream, container.wait().await.system_task())
.await
.expect("failed to start component runner");
})
.detach();
}
KernelServices::ContainerController(stream) => {
let container = container.clone();
fuchsia_async::Task::local(async move {
serve_container_controller(stream, container.wait().await.system_task())
.await
.expect("failed to start container controller");
})
.detach();
}
KernelServices::MemoryAttributionProvider(stream) => {
let container = container.clone();
fuchsia_async::Task::local(async move {
serve_memory_attribution_provider_elfkernel(stream, container.wait().await)
.await
.expect("failed to start memory attribution provider");
})
.detach();
}
}
}
Ok(())
}