Skip to main content

starnix_kernel_runner/
container.rs

1// Copyright 2028 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use crate::{
6    Features, MountAction, expose_root, parse_features, parse_numbered_handles,
7    run_container_features, serve_component_runner, serve_container_controller,
8    serve_graphical_presenter, serve_lutex_controller,
9};
10use anyhow::{Context, Error, anyhow, bail};
11use bootreason::get_or_init_android_bootreason;
12use bstr::{BString, ByteSlice};
13use devicetree::parser::parse_devicetree;
14use devicetree::types::Devicetree;
15use fidl::endpoints::{ControlHandle, RequestStream, ServerEnd};
16use fidl_fuchsia_boot as fboot;
17use fidl_fuchsia_component as fcomponent;
18use fidl_fuchsia_component_runner as frunner;
19use fidl_fuchsia_component_runner::{TaskProviderRequest, TaskProviderRequestStream};
20use fidl_fuchsia_element as felement;
21use fidl_fuchsia_feedback::CrashReporterMarker;
22use fidl_fuchsia_io as fio;
23use fidl_fuchsia_mem as fmem;
24use fidl_fuchsia_memory_attribution as fattribution;
25use fidl_fuchsia_starnix_binder as fbinder;
26use fidl_fuchsia_starnix_container as fstarcontainer;
27use fidl_fuchsia_sysinfo as fsysinfo;
28use fidl_fuchsia_time_external::AdjustMarker;
29use fuchsia_async as fasync;
30use fuchsia_async::DurationExt;
31use fuchsia_component::client::{connect_to_protocol, connect_to_protocol_sync};
32use fuchsia_component::server::ServiceFs;
33use fuchsia_inspect as inspect;
34use fuchsia_runtime as fruntime;
35use fuchsia_zbi as zbi;
36use futures::channel::oneshot;
37use futures::{FutureExt, StreamExt, TryStreamExt};
38use serde::Deserialize;
39use starnix_container_structured_config::Config as ContainerStructuredConfig;
40use starnix_core::device::remote_block_device::remote_block_device_init;
41use starnix_core::execution::{
42    create_init_process, create_system_task, execute_task_with_prerun_result,
43};
44use starnix_core::fs::fuchsia::new_remotefs_in_root;
45use starnix_core::fs::tmpfs::TmpFs;
46use starnix_core::security;
47use starnix_core::task::container_namespace::ContainerNamespace;
48use starnix_core::task::{
49    CurrentTask, ExitStatus, Kernel, RoleOverrides, SchedulerManager, parse_cmdline,
50};
51use starnix_core::vfs::{FileSystemOptions, FsContext, LookupContext, Namespace, WhatToMount};
52use starnix_logging::{
53    CATEGORY_STARNIX, NAME_CREATE_CONTAINER, log_debug, log_error, log_info, log_warn,
54};
55use starnix_modules::{
56    init_common_devices, register_common_file_systems, register_common_syscalls,
57};
58use starnix_modules_layeredfs::{LayeredFsBuilder, LayeredFsMounts};
59use starnix_modules_magma::get_magma_params;
60use starnix_modules_overlayfs::OverlayStack;
61use starnix_modules_rtc::rtc_device_init;
62use starnix_sync::{Locked, Unlocked};
63use starnix_task_command::TaskCommand;
64use starnix_uapi::errors::{ENOENT, SourceContext};
65use starnix_uapi::open_flags::OpenFlags;
66use starnix_uapi::resource_limits::Resource;
67use starnix_uapi::{errno, tid_t};
68use std::ffi::CString;
69use std::ops::DerefMut;
70use std::sync::Arc;
71use zx::Task as _;
72
73use std::sync::Weak;
74
75use crate::serve_memory_attribution_provider_container;
76use attribution_server::{AttributionServer, AttributionServerHandle};
77
78/// Manages the memory attribution protocol for a Starnix container.
79struct ContainerMemoryAttributionManager {
80    /// Holds state for the hanging-get attribution protocol.
81    memory_attribution_server: AttributionServerHandle,
82}
83
84impl ContainerMemoryAttributionManager {
85    /// Creates a new [ContainerMemoryAttributionManager] from a Starnix kernel and the moniker
86    /// token of the container component.
87    pub fn new(kernel: Weak<Kernel>, component_instance: zx::Event) -> Self {
88        let memory_attribution_server = AttributionServer::new(Box::new(move || {
89            let kernel_ref = match kernel.upgrade() {
90                None => return vec![],
91                Some(k) => k,
92            };
93            attribution_info_for_kernel(kernel_ref.as_ref(), &component_instance)
94        }));
95
96        ContainerMemoryAttributionManager { memory_attribution_server }
97    }
98
99    /// Creates a new observer for the attribution information from this container.
100    pub fn new_observer(
101        &self,
102        control_handle: fattribution::ProviderControlHandle,
103    ) -> attribution_server::Observer {
104        self.memory_attribution_server.new_observer(control_handle)
105    }
106}
107
108/// Generates the attribution information for the Starnix kernel ELF component. The attribution
109/// information for the container is handled by the container component, not the kernel
110/// component itself, even if both are hosted within the same kernel process.
111fn attribution_info_for_kernel(
112    kernel: &Kernel,
113    component_instance: &zx::Event,
114) -> Vec<fattribution::AttributionUpdate> {
115    // Start the server to handle the memory attribution requests for the container, and provide
116    // a handle to get detailed attribution. We start a new task as each incoming connection is
117    // independent.
118    let (client_end, server_end) =
119        fidl::endpoints::create_request_stream::<fattribution::ProviderMarker>();
120    fuchsia_async::Task::spawn(serve_memory_attribution_provider_container(server_end, kernel))
121        .detach();
122
123    let starnix_kernel_id = Some(1);
124    let starnix_kernel_principal = fattribution::NewPrincipal {
125        identifier: starnix_kernel_id,
126        description: Some(fattribution::Description::Part("starnix_kernel".to_string())),
127        principal_type: Some(fattribution::PrincipalType::Part),
128        // This part is created for accounting. It holds the resource used for starnix
129        // kernel operation. It neither has sub-principals, nor publishes attribution,
130        // hence it does not need to be tied to a provider server end.
131        detailed_attribution: None,
132        ..Default::default()
133    };
134
135    let starnix_kernel_attribution = fattribution::UpdatedPrincipal {
136        identifier: starnix_kernel_id, // Recipient.
137        resources: Some(fattribution::Resources::Data(fattribution::Data {
138            resources: vec![fattribution::Resource::ProcessMapped(fattribution::ProcessMapped {
139                process: fuchsia_runtime::process_self().koid().unwrap().raw_koid(),
140                base: 0, // Attribute all the range.
141                len: u64::max_value(),
142                hint_skip_handle_table: false,
143            })],
144        })),
145        ..Default::default()
146    };
147
148    let container_id = Some(2);
149    let new_principal = fattribution::NewPrincipal {
150        identifier: container_id,
151        description: Some(fattribution::Description::Component(
152            component_instance.duplicate_handle(zx::Rights::SAME_RIGHTS).unwrap(),
153        )),
154        principal_type: Some(fattribution::PrincipalType::Runnable),
155        detailed_attribution: Some(client_end),
156        ..Default::default()
157    };
158    let attribution = fattribution::UpdatedPrincipal {
159        identifier: container_id,
160        resources: Some(fattribution::Resources::Data(fattribution::Data {
161            resources: vec![fattribution::Resource::KernelObject(
162                fuchsia_runtime::job_default().koid().unwrap().raw_koid(),
163            )],
164        })),
165        ..Default::default()
166    };
167
168    vec![
169        fattribution::AttributionUpdate::Add(new_principal),
170        fattribution::AttributionUpdate::Add(starnix_kernel_principal),
171        fattribution::AttributionUpdate::Update(attribution),
172        fattribution::AttributionUpdate::Update(starnix_kernel_attribution),
173    ]
174}
175
176#[derive(Debug)]
177pub struct ContainerStartInfo {
178    /// Configuration specified by the component's `program` block.
179    pub program: ContainerProgram,
180
181    pub config: ContainerStructuredConfig,
182
183    /// The outgoing directory of the container, used to serve protocols on behalf of the container.
184    /// For example, the starnix_kernel serves a component runner in the containers' outgoing
185    /// directory.
186    outgoing_dir: Option<zx::Channel>,
187
188    /// Mapping of top-level namespace entries to an associated channel.
189    /// For example, "/svc" to the respective channel.
190    pub container_namespace: ContainerNamespace,
191
192    /// The runtime directory of the container, used to provide CF introspection.
193    runtime_dir: Option<ServerEnd<fio::DirectoryMarker>>,
194
195    /// An eventpair that debuggers can use to defer the launch of the container.
196    break_on_start: Option<zx::EventPair>,
197
198    /// Component moniker token for the container component. This token is used in various protocols
199    /// to uniquely identify a component.
200    component_instance: Option<zx::Event>,
201}
202
203const MISSING_CONFIG_VMO_CONTEXT: &str = concat!(
204    "Retrieving container config VMO. ",
205    "If this fails, make sure your container CML includes ",
206    "//src/starnix/containers/container.shard.cml.",
207);
208
209impl ContainerStartInfo {
210    fn new(mut start_info: frunner::ComponentStartInfo) -> Result<Self, Error> {
211        let program = start_info.program.as_ref().context("retrieving program block")?;
212        let program: ContainerProgram =
213            runner::serde::deserialize_program(&program).context("parsing program block")?;
214
215        let encoded_config =
216            start_info.encoded_config.as_ref().context(MISSING_CONFIG_VMO_CONTEXT)?;
217        let config = match encoded_config {
218            fmem::Data::Bytes(b) => ContainerStructuredConfig::from_bytes(b),
219            fmem::Data::Buffer(b) => ContainerStructuredConfig::from_vmo(&b.vmo),
220            other => anyhow::bail!("unknown Data variant {other:?}"),
221        }
222        .context("parsing container structured config")?;
223
224        let ns = start_info.ns.take().context("retrieving container namespace")?;
225        let container_namespace = ContainerNamespace::from(ns);
226
227        let outgoing_dir = start_info.outgoing_dir.take().map(|dir| dir.into_channel());
228        let component_instance = start_info.component_instance;
229
230        Ok(Self {
231            program,
232            config,
233            outgoing_dir,
234            container_namespace,
235            component_instance,
236            break_on_start: start_info.break_on_start,
237            runtime_dir: start_info.runtime_dir,
238        })
239    }
240}
241
242#[derive(Debug, Default, Deserialize)]
243#[serde(deny_unknown_fields)]
244pub struct ContainerProgram {
245    /// The name of this container.
246    name: String,
247
248    /// The command line for the initial process for this container.
249    init: Vec<String>,
250
251    /// The command line for the kernel.
252    #[serde(default)]
253    kernel_cmdline: String,
254
255    /// The specifications for the file system mounts for this container.
256    #[serde(default)]
257    mounts: Vec<String>,
258
259    /// The features enabled for this container.
260    #[serde(default)]
261    pub features: Vec<String>,
262
263    /// The resource limits to apply to this container.
264    #[serde(default)]
265    rlimits: Vec<String>,
266
267    /// The path that the container will wait until exists before considering itself to have started.
268    #[serde(default)]
269    startup_file_path: String,
270
271    /// The default seclabel that is applied to components that are instantiated in this container.
272    ///
273    /// Components can override this by setting the `seclabel` field in their program block.
274    #[serde(default)]
275    pub default_seclabel: Option<String>,
276
277    /// The default uid that is applied to components that are instantiated in this container.
278    ///
279    /// Components can override this by setting the `uid` field in their program block.
280    #[serde(default = "default_uid")]
281    pub default_uid: runner::serde::StoreAsString<u32>,
282
283    /// The default mount options to use when mounting directories from a component's namespace.
284    ///
285    /// Each string is expected to follow the format: "<namespace_path>:<mount_options>".
286    pub default_ns_mount_options: Option<Vec<String>>,
287
288    /// Specifies role names to use for "realtime" tasks based on their process & thread names.
289    ///
290    /// Zircon's scheduler doesn't support configuring tasks to always preempt non-"realtime"
291    /// tasks without specifying a constant bandwidth profile. These profiles specify the period and
292    /// expected runtime of a "realtime" task, bounding the amount of work it is allowed to perform
293    /// at an elevated "realtime" priority.
294    ///
295    /// Because constant bandwidth profiles require workload-specific tuning, we can't uniformly
296    /// apply a single profile for all "realtime" tasks. Instead, this container configuration
297    /// allows us to specify different constant bandwidth profiles for different workloads.
298    #[serde(default)]
299    task_role_overrides: Vec<TaskSchedulerMapping>,
300}
301
302/// Specifies a role override for a class of tasks whose process and thread names match provided
303/// patterns.
304#[derive(Default, Deserialize)]
305struct TaskSchedulerMapping {
306    /// The role name to use for tasks matching the provided patterns.
307    role: String,
308    /// A regular expression that will be matched against the process' command.
309    process: String,
310    /// A regular expression that will be matched against the thread's command.
311    thread: String,
312}
313
314impl std::fmt::Debug for TaskSchedulerMapping {
315    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
316        write!(f, "process `{}` thread `{}` role `{}`", self.process, self.thread, self.role)
317    }
318}
319
320fn default_uid() -> runner::serde::StoreAsString<u32> {
321    runner::serde::StoreAsString(42)
322}
323
324// Creates a CString from a String. Calling this with an invalid CString will panic.
325fn to_cstr(str: &str) -> CString {
326    CString::new(str.to_string()).unwrap()
327}
328
329#[must_use = "The container must run serve on this config"]
330pub struct ContainerServiceConfig {
331    start_info: ContainerStartInfo,
332    request_stream: frunner::ComponentControllerRequestStream,
333    receiver: oneshot::Receiver<Result<ExitStatus, Error>>,
334}
335
336pub struct Container {
337    /// The `Kernel` object that is associated with the container.
338    pub kernel: Arc<Kernel>,
339
340    memory_attribution_manager: ContainerMemoryAttributionManager,
341
342    /// Inspect node holding information about the state of the container.
343    _node: inspect::Node,
344
345    /// Until negative trait bound are implemented, using `*mut u8` to prevent transferring
346    /// Container across threads.
347    _thread_bound: std::marker::PhantomData<*mut u8>,
348}
349
350impl Container {
351    pub fn system_task(&self) -> &CurrentTask {
352        self.kernel.kthreads.system_task()
353    }
354
355    async fn serve_outgoing_directory(
356        &self,
357        outgoing_dir: Option<zx::Channel>,
358    ) -> Result<(), Error> {
359        if let Some(outgoing_dir) = outgoing_dir {
360            // Add `ComponentRunner` to the exposed services of the container, and then serve the
361            // outgoing directory.
362            let mut fs = ServiceFs::new_local();
363            fs.dir("svc")
364                .add_fidl_service(ExposedServices::ComponentRunner)
365                .add_fidl_service(ExposedServices::ContainerController)
366                .add_fidl_service(ExposedServices::GraphicalPresenter)
367                .add_fidl_service(ExposedServices::LutexController);
368
369            // Expose the root of the container's filesystem.
370            let (fs_root, fs_root_server_end) = fidl::endpoints::create_proxy();
371            fs.add_remote("fs_root", fs_root);
372            expose_root(
373                self.kernel.kthreads.unlocked_for_async().deref_mut(),
374                self.system_task(),
375                fs_root_server_end,
376            )?;
377
378            fs.serve_connection(outgoing_dir.into()).map_err(|_| errno!(EINVAL))?;
379
380            fs.for_each_concurrent(None, |request_stream| async {
381                match request_stream {
382                    ExposedServices::ComponentRunner(request_stream) => {
383                        match serve_component_runner(request_stream, self.system_task()).await {
384                            Ok(_) => {}
385                            Err(e) => {
386                                log_error!("Error serving component runner: {:?}", e);
387                            }
388                        }
389                    }
390                    ExposedServices::ContainerController(request_stream) => {
391                        serve_container_controller(request_stream, self.system_task())
392                            .await
393                            .expect("failed to start container.")
394                    }
395                    ExposedServices::GraphicalPresenter(request_stream) => {
396                        serve_graphical_presenter(request_stream, &self.kernel)
397                            .await
398                            .expect("failed to start GraphicalPresenter.")
399                    }
400                    ExposedServices::LutexController(request_stream) => {
401                        serve_lutex_controller(request_stream, self.system_task())
402                            .await
403                            .expect("failed to start LutexController.")
404                    }
405                }
406            })
407            .await
408        }
409        Ok(())
410    }
411
412    pub async fn serve(&self, service_config: ContainerServiceConfig) -> Result<(), Error> {
413        let (r, _) = futures::join!(
414            self.serve_outgoing_directory(service_config.start_info.outgoing_dir),
415            server_component_controller(
416                self.kernel.clone(),
417                service_config.request_stream,
418                service_config.receiver
419            )
420        );
421        r
422    }
423
424    pub fn new_memory_attribution_observer(
425        &self,
426        control_handle: fattribution::ProviderControlHandle,
427    ) -> attribution_server::Observer {
428        self.memory_attribution_manager.new_observer(control_handle)
429    }
430}
431
432/// The services that are exposed in the container component's outgoing directory.
433enum ExposedServices {
434    ComponentRunner(frunner::ComponentRunnerRequestStream),
435    ContainerController(fstarcontainer::ControllerRequestStream),
436    GraphicalPresenter(felement::GraphicalPresenterRequestStream),
437    LutexController(fbinder::LutexControllerRequestStream),
438}
439
440type TaskResult = Result<ExitStatus, Error>;
441
442async fn server_component_controller(
443    kernel: Arc<Kernel>,
444    request_stream: frunner::ComponentControllerRequestStream,
445    task_complete: oneshot::Receiver<TaskResult>,
446) {
447    *kernel.container_control_handle.lock() = Some(request_stream.control_handle());
448
449    enum Event<T, U> {
450        Controller(T),
451        Completion(U),
452    }
453
454    let mut stream = futures::stream::select(
455        request_stream.map(Event::Controller),
456        task_complete.into_stream().map(Event::Completion),
457    );
458
459    while let Some(event) = stream.next().await {
460        match event {
461            Event::Controller(Ok(frunner::ComponentControllerRequest::Stop { .. })) => {
462                log_info!("Stopping the container.");
463            }
464            Event::Controller(Ok(frunner::ComponentControllerRequest::Kill { control_handle })) => {
465                log_info!("Killing the container's job.");
466                control_handle.shutdown_with_epitaph(zx::Status::from_raw(
467                    fcomponent::Error::InstanceDied.into_primitive() as i32,
468                ));
469                fruntime::job_default().kill().expect("Failed to kill job");
470            }
471            Event::Controller(Ok(frunner::ComponentControllerRequest::_UnknownMethod {
472                ordinal,
473                method_type,
474                ..
475            })) => {
476                log_error!(ordinal, method_type:?; "Unknown component controller request received.");
477            }
478            Event::Controller(Err(e)) => {
479                log_warn!(e:?; "Container component controller channel encountered an error.");
480            }
481            Event::Completion(result) => {
482                log_info!(result:?; "init process exited.");
483            }
484        }
485
486        // We treat any event in the stream as an invitation to shut down.
487        if !kernel.is_shutting_down() {
488            kernel.shut_down();
489        }
490    }
491
492    log_debug!("done listening for container-terminating events");
493
494    // In case the stream ended without an event, shut down the kernel here.
495    if !kernel.is_shutting_down() {
496        kernel.shut_down();
497    }
498}
499
500pub async fn create_component_from_stream(
501    mut request_stream: frunner::ComponentRunnerRequestStream,
502    kernel_extra_features: Vec<String>,
503) -> Result<(Container, ContainerServiceConfig), Error> {
504    if let Some(event) = request_stream.try_next().await? {
505        match event {
506            frunner::ComponentRunnerRequest::Start { start_info, controller, .. } => {
507                let request_stream = controller.into_stream();
508                let mut start_info = ContainerStartInfo::new(start_info)?;
509                let (sender, receiver) = oneshot::channel::<TaskResult>();
510                let container = create_container(&mut start_info, &kernel_extra_features, sender)
511                    .await
512                    .with_source_context(|| {
513                        format!("creating container \"{}\"", start_info.program.name)
514                    })?;
515                let service_config =
516                    ContainerServiceConfig { start_info, request_stream, receiver };
517                return Ok((container, service_config));
518            }
519            frunner::ComponentRunnerRequest::_UnknownMethod { ordinal, .. } => {
520                log_warn!("Unknown ComponentRunner request: {ordinal}");
521            }
522        }
523    }
524    bail!("did not receive Start request");
525}
526
527async fn get_bootargs(device_tree: &Devicetree) -> Result<String, Error> {
528    device_tree
529        .root_node
530        .find("chosen")
531        .and_then(|n| {
532            n.get_property("bootargs").map(|p| {
533                let end =
534                    if p.value.last() == Some(&0) { p.value.len() - 1 } else { p.value.len() };
535                match std::str::from_utf8(&p.value[..end]) {
536                    Ok(s) => Ok(s.to_owned()),
537                    Err(e) => {
538                        log_warn!("Bootargs are not valid UTF-8: {e}");
539                        Err(anyhow!("Bootargs are not valid UTF-8"))
540                    }
541                }
542            })
543        })
544        .context("Couldn't find bootargs")?
545}
546
547async fn get_bootitems() -> Result<std::vec::Vec<u8>, Error> {
548    let items =
549        connect_to_protocol::<fboot::ItemsMarker>().context("Failed to connect to boot items")?;
550
551    let items_response = items
552        .get2(zbi::ZbiType::DeviceTree.into_raw(), None)
553        .await
554        .context("FIDL: Failed to get devicetree item")?
555        .map_err(|e| anyhow!("Failed to get devicetree item {:?}", e))?;
556
557    let Some(item) = items_response.last() else {
558        return Err(anyhow!("Failed to get items"));
559    };
560
561    let devicetree_vmo = &item.payload;
562    let bytes = devicetree_vmo
563        .read_to_vec(0, item.length as u64)
564        .context("Failed to read devicetree vmo")?;
565
566    Ok(bytes)
567}
568
569async fn get_serial_number() -> Result<String, Error> {
570    let sysinfo = connect_to_protocol::<fsysinfo::SysInfoMarker>()
571        .context("Failed to connect to fuchsia.sysinfo.SysInfo")?;
572    sysinfo
573        .get_serial_number()
574        .await
575        .context("FIDL: Failed to get serial number")?
576        .map_err(|status| anyhow!("Failed to get serial number: {:?}", status))
577}
578
579async fn create_container(
580    start_info: &mut ContainerStartInfo,
581    kernel_extra_features: &[String],
582    task_complete: oneshot::Sender<TaskResult>,
583) -> Result<Container, Error> {
584    fuchsia_trace::duration!(CATEGORY_STARNIX, NAME_CREATE_CONTAINER);
585    const DEFAULT_INIT: &str = "/container/init";
586
587    let pkg_channel = start_info.container_namespace.get_namespace_channel("/pkg").unwrap();
588    let pkg_dir_proxy = fio::DirectorySynchronousProxy::new(pkg_channel);
589
590    let device_tree: Option<Devicetree> = match get_bootitems().await {
591        Ok(items) => match parse_devicetree(&items) {
592            Ok(device_tree) => Some(device_tree),
593            Err(e) => {
594                log_warn!("Failed to parse devicetree: {e:?}");
595                None
596            }
597        },
598        Err(e) => {
599            log_warn!("Failed to get boot items for devicetree: {e:?}");
600            None
601        }
602    };
603    let mut features = parse_features(&start_info, kernel_extra_features)?;
604
605    log_debug!("Creating container with {:#?}", features);
606    let mut kernel_cmdline = BString::from(start_info.program.kernel_cmdline.as_bytes());
607    let mut android_provided_bootreason = None;
608
609    let mut bootargs_has_serialno = false;
610    // TODO(https://fxbug.dev/526770691): Newer versions of Android replace the 'androidboot.*'
611    // kernel parameters with 'bootconfig'. We should expose this to the container to avoid having
612    // to manage 'androidboot.*' parameters here.
613    if features.android_serialno {
614        if let Some(device_tree) = &device_tree {
615            match get_bootargs(device_tree).await {
616                Ok(args) => {
617                    for item in parse_cmdline(&args) {
618                        if item.starts_with("androidboot.force_normal_boot") {
619                            // TODO(https://fxbug.dev/424152964): Support force_normal_boot.
620                            continue;
621                        }
622                        if item.starts_with("androidboot.serialno") {
623                            bootargs_has_serialno = true;
624                        }
625                        if item.starts_with("androidboot.bootreason") && features.android_bootreason
626                        {
627                            // androidboot.bootreason is sourced from the Fuchsia reboot reason.
628                            // It is still useful to log it from userspace to learn what the
629                            // possible values are.
630                            log_info!("Original devicetree bootarg {:?}", item);
631                            if let Some((_, v)) = item.split_once('=') {
632                                android_provided_bootreason = Some(v.to_string());
633                            }
634                            continue;
635                        }
636                        kernel_cmdline.extend(b" ");
637                        kernel_cmdline.extend(item.bytes());
638                    }
639                }
640                Err(err) => log_warn!("could not get bootargs: {err:?}"),
641            }
642        } else {
643            log_warn!("No devicetree available to get bootargs for android.serialno");
644        }
645
646        if !bootargs_has_serialno {
647            match get_serial_number().await {
648                Ok(serial) => {
649                    log_info!("Fell back to sysinfo serial number: {}", serial);
650                    kernel_cmdline.extend(b" androidboot.serialno=");
651                    kernel_cmdline.extend(serial.bytes());
652                }
653                Err(err) => {
654                    log_warn!("Could not get serial number from sysinfo: {err:?}");
655                }
656            }
657        }
658    }
659    if features.android_bootreason {
660        kernel_cmdline.extend(b" androidboot.bootreason=");
661
662        let tmp_channel = start_info.container_namespace.get_namespace_channel("/tmp_lifecycle");
663        let tmp_proxy = match tmp_channel {
664            Ok(channel) => {
665                Some(fio::DirectoryProxy::new(fidl::AsyncChannel::from_channel(channel)))
666            }
667            _ => None,
668        };
669
670        match get_or_init_android_bootreason(tmp_proxy, android_provided_bootreason).await {
671            Ok(reason) => {
672                kernel_cmdline.extend(reason.bytes());
673            }
674            Err(err) => {
675                log_warn!("could not get android bootreason: {err:?}. falling back to 'unknown'");
676                kernel_cmdline.extend(b"unknown");
677            }
678        }
679    }
680    if let Some(supported_vendors) = &features.magma_supported_vendors {
681        kernel_cmdline.extend(b" ");
682        let params = get_magma_params(supported_vendors);
683        kernel_cmdline.extend(&*params);
684    }
685
686    // Check whether we actually have access to a role manager by trying to set our own
687    // thread's role.
688    let mut task_mappings = RoleOverrides::new();
689    for m in &start_info.program.task_role_overrides {
690        task_mappings.add(m.process.clone(), m.thread.clone(), m.role.clone());
691    }
692    let task_mappings = task_mappings.build().context("adding custom task role")?;
693    let scheduler_manager = SchedulerManager::new(task_mappings);
694
695    let crash_reporter = connect_to_protocol::<CrashReporterMarker>().unwrap();
696
697    let node = inspect::component::inspector().root().create_child("container");
698    let kernel_node = node.create_child("kernel");
699    kernel_node.record_int("created_at", zx::MonotonicInstant::get().into_nanos());
700    features.record_inspect(&kernel_node);
701
702    let security_state = security::kernel_init_security(
703        features.selinux.enabled,
704        features.selinux.options.clone(),
705        features.selinux.exceptions.clone(),
706        &kernel_node,
707    );
708
709    // `config.enable_utc_time_adjustment` is set through config capability
710    // `fuchsia.time.config.WritableUTCTime`.
711    let time_adjustment_proxy = if features.enable_utc_time_adjustment {
712        connect_to_protocol_sync::<AdjustMarker>()
713            .map_err(|e| log_error!("could not connect to fuchsia.time.external/Adjust: {:?}", e))
714            .ok()
715    } else {
716        // See the comment above. UTC adjustment is a per-product setting.
717        log_info!("UTC adjustment is forbidden.");
718        None
719    };
720
721    log_info!("final kernel cmdline: {kernel_cmdline:?}");
722    kernel_node.record_string("cmdline", kernel_cmdline.to_str_lossy());
723
724    let kernel = Kernel::new(
725        kernel_cmdline,
726        features.kernel.clone(),
727        std::mem::take(&mut features.system_limits),
728        start_info.container_namespace.try_clone()?,
729        scheduler_manager,
730        Some(crash_reporter),
731        kernel_node,
732        security_state,
733        time_adjustment_proxy,
734        device_tree,
735    )
736    .with_source_context(|| format!("creating Kernel: {}", start_info.program.name))?;
737    let (fs_context, feature_mounts) = create_fs_context(
738        kernel.kthreads.unlocked_for_async().deref_mut(),
739        &kernel,
740        &features,
741        start_info,
742        &pkg_dir_proxy,
743    )
744    .source_context("creating FsContext")?;
745    let init_pid = kernel.pids.write().allocate_pid();
746    // Lots of software assumes that the pid for the init process is 1.
747    debug_assert_eq!(init_pid, 1);
748
749    let system_task = create_system_task(
750        kernel.kthreads.unlocked_for_async().deref_mut(),
751        &kernel,
752        Arc::clone(&fs_context),
753    )
754    .source_context("create system task")?;
755    // The system task gives pid 2. This value is less critical than giving
756    // pid 1 to init, but this value matches what is supposed to happen.
757    debug_assert_eq!(system_task.tid, 2);
758
759    feature_mounts(kernel.kthreads.unlocked_for_async().deref_mut(), &system_task)
760        .source_context("mounting feature filesystems")?;
761
762    kernel.kthreads.init(system_task).source_context("initializing kthreads")?;
763    let system_task = kernel.kthreads.system_task();
764
765    kernel.syslog.init(&system_task).source_context("initializing syslog")?;
766
767    kernel.hrtimer_manager.init(system_task).source_context("initializing HrTimer manager")?;
768
769    log_info!("Initializing suspend resume manager.");
770    if let Err(e) = kernel.suspend_resume_manager.init(&system_task) {
771        log_warn!("Suspend/Resume manager initialization failed: ({e:?})");
772    }
773
774    // Real Time clock is present in all configuration.
775    log_info!("Initializing RTC device.");
776    rtc_device_init(kernel.kthreads.unlocked_for_async().deref_mut(), &system_task)
777        .context("in starnix_kernel_runner, while initializing RTC")?;
778
779    // Register common devices and add them in sysfs and devtmpfs.
780    log_info!("Registering devices and filesystems.");
781    init_common_devices(kernel.kthreads.unlocked_for_async().deref_mut(), &kernel)?;
782    register_common_file_systems(kernel.kthreads.unlocked_for_async().deref_mut(), &kernel);
783
784    register_common_syscalls(&kernel);
785
786    log_info!("Mounting filesystems.");
787    mount_filesystems(
788        kernel.kthreads.unlocked_for_async().deref_mut(),
789        &system_task,
790        start_info,
791        &pkg_dir_proxy,
792    )
793    .source_context("mounting filesystems")?;
794
795    // Run all common features that were specified in the .cml.
796    {
797        log_info!("Running container features.");
798        run_container_features(
799            kernel.kthreads.unlocked_for_async().deref_mut(),
800            &system_task,
801            &features,
802        )?;
803    }
804
805    log_info!("Initializing remote block devices.");
806    init_remote_block_devices(kernel.kthreads.unlocked_for_async().deref_mut(), &system_task)
807        .source_context("initalizing remote block devices")?;
808
809    // If there is an init binary path, run it, optionally waiting for the
810    // startup_file_path to be created. The task struct is still used
811    // to initialize the system up until this point, regardless of whether
812    // or not there is an actual init to be run.
813    let argv = if start_info.program.init.is_empty() {
814        vec![DEFAULT_INIT.to_string()]
815    } else {
816        start_info.program.init.clone()
817    }
818    .iter()
819    .map(|s| to_cstr(s))
820    .collect::<Vec<_>>();
821
822    log_info!("Opening start_info file.");
823    let executable = system_task
824        .open_file(
825            kernel.kthreads.unlocked_for_async().deref_mut(),
826            argv[0].as_bytes().into(),
827            OpenFlags::RDONLY,
828        )
829        .with_source_context(|| format!("opening init: {:?}", argv[0]))?;
830
831    let initial_name = if start_info.program.init.is_empty() {
832        TaskCommand::default()
833    } else {
834        TaskCommand::new(start_info.program.init[0].as_bytes())
835    };
836
837    let rlimits = parse_rlimits(&start_info.program.rlimits)?;
838
839    // Serve the runtime directory.
840    log_info!("Starting runtime directory.");
841    if let Some(runtime_dir) = start_info.runtime_dir.take() {
842        kernel.kthreads.spawn_future(
843            move || async move { serve_runtime_dir(runtime_dir).await },
844            "serve_runtime_dir",
845        );
846    }
847
848    // At this point the runtime environment has been prepared but nothing is actually running yet.
849    // Pause here if a debugger needs time to attach to the job.
850    if let Some(break_on_start) = start_info.break_on_start.take() {
851        log_info!("Waiting for signal from debugger before spawning init process...");
852        if let Err(e) =
853            fuchsia_async::OnSignals::new(break_on_start, zx::Signals::EVENTPAIR_PEER_CLOSED).await
854        {
855            log_warn!(e:%; "Received break_on_start eventpair but couldn't wait for PEER_CLOSED.");
856        }
857    }
858
859    log_info!("Creating init process.");
860    let init_task = create_init_process(
861        kernel.kthreads.unlocked_for_async().deref_mut(),
862        &kernel,
863        init_pid,
864        initial_name,
865        Arc::clone(&fs_context),
866        &rlimits,
867    )
868    .with_source_context(|| format!("creating init task: {:?}", start_info.program.init))?;
869
870    execute_task_with_prerun_result(
871        kernel.kthreads.unlocked_for_async().deref_mut(),
872        init_task,
873        move |locked, init_task| {
874            parse_numbered_handles(locked, init_task, None, &init_task.files()).expect("");
875            init_task.exec(locked, executable, argv[0].clone(), argv.clone(), vec![])
876        },
877        move |result| {
878            log_info!("Finished running init process: {:?}", result);
879            let _ = task_complete.send(result);
880        },
881        None,
882    )?;
883
884    if !start_info.program.startup_file_path.is_empty() {
885        wait_for_init_file(&start_info.program.startup_file_path, &system_task, init_pid).await?;
886    };
887
888    let memory_attribution_manager = ContainerMemoryAttributionManager::new(
889        Arc::downgrade(&kernel),
890        start_info.component_instance.take().ok_or_else(|| Error::msg("No component instance"))?,
891    );
892
893    Ok(Container {
894        kernel,
895        memory_attribution_manager,
896        _node: node,
897        _thread_bound: Default::default(),
898    })
899}
900
901fn create_fs_context(
902    locked: &mut Locked<Unlocked>,
903    kernel: &Kernel,
904    features: &Features,
905    start_info: &ContainerStartInfo,
906    pkg_dir_proxy: &fio::DirectorySynchronousProxy,
907) -> Result<(Arc<FsContext>, LayeredFsMounts), Error> {
908    // The mounts are applied in the order listed. Mounting will fail if the designated mount
909    // point doesn't exist in a previous mount. The root must be first so other mounts can be
910    // applied on top of it.
911    let mut mounts_iter =
912        start_info.program.mounts.iter().chain(start_info.config.additional_mounts.iter());
913    let root = MountAction::new_for_root(
914        locked,
915        kernel,
916        pkg_dir_proxy,
917        mounts_iter.next().ok_or_else(|| anyhow!("Mounts list is empty"))?,
918    )?;
919    if root.path != "/" {
920        anyhow::bail!("First mount in mounts list is not the root");
921    }
922
923    let mut builder = LayeredFsBuilder::new(root.fs);
924    if features.container {
925        // /container/component will be a tmpfs where component using the starnix kernel will have their
926        // package mounted.
927        let component_tmpfs_options = FileSystemOptions {
928            params: kernel
929                .features
930                .ns_mount_options("#component_tmpfs")
931                .context("#component_tmpfs options")?,
932            ..Default::default()
933        };
934        let component_tmpfs = TmpFs::new_fs_with_options(locked, kernel, component_tmpfs_options)?;
935
936        // /container will mount the container pkg
937        let container_remotefs_options = FileSystemOptions {
938            source: "data".into(),
939            params: kernel.features.ns_mount_options("#container").context("#container options")?,
940            ..Default::default()
941        };
942        let container_remotefs = new_remotefs_in_root(
943            locked,
944            kernel,
945            pkg_dir_proxy,
946            container_remotefs_options,
947            fio::PERM_READABLE | fio::PERM_EXECUTABLE,
948        )?;
949
950        builder.add("/container", container_remotefs);
951        builder.add("/container/component", component_tmpfs);
952    }
953    if features.custom_artifacts {
954        let mount_options = FileSystemOptions {
955            params: kernel
956                .features
957                .ns_mount_options("#custom_artifacts")
958                .context("#custom_artifacts options")?,
959            ..Default::default()
960        };
961        let fs = TmpFs::new_fs_with_options(locked, kernel, mount_options)?;
962        builder.add("/custom_artifacts", fs);
963    }
964    if features.test_data {
965        let mount_options = FileSystemOptions {
966            params: kernel.features.ns_mount_options("#test_data").context("#test_data options")?,
967            ..Default::default()
968        };
969        let fs = TmpFs::new_fs_with_options(locked, kernel, mount_options)?;
970        builder.add("/test_data", fs);
971    }
972
973    let (mut root_fs, feature_mounts) = builder.build(locked, kernel);
974    if features.rootfs_rw {
975        root_fs = OverlayStack::wrap_fs_in_writable_layer(locked, kernel, root_fs)?;
976    }
977
978    Ok((FsContext::new(Namespace::new_with_flags(root_fs, root.flags)), feature_mounts))
979}
980
981fn parse_rlimits(rlimits: &[String]) -> Result<Vec<(Resource, u64)>, Error> {
982    let mut res = Vec::new();
983
984    for rlimit in rlimits {
985        let (key, value) =
986            rlimit.split_once('=').ok_or_else(|| anyhow!("Invalid rlimit: {rlimit}"))?;
987        let value = value.parse::<u64>()?;
988        let kv = match key {
989            "RLIMIT_NOFILE" => (Resource::NOFILE, value),
990            "RLIMIT_RTPRIO" => (Resource::RTPRIO, value),
991            _ => bail!("Unknown rlimit: {key}"),
992        };
993        res.push(kv);
994    }
995
996    Ok(res)
997}
998
999fn mount_filesystems(
1000    locked: &mut Locked<Unlocked>,
1001    system_task: &CurrentTask,
1002    start_info: &ContainerStartInfo,
1003    pkg_dir_proxy: &fio::DirectorySynchronousProxy,
1004) -> Result<(), Error> {
1005    // Skip the first mount, that was used to create the root filesystem.
1006    let mut mounts_iter =
1007        start_info.program.mounts.iter().chain(start_info.config.additional_mounts.iter());
1008    let _ = mounts_iter.next();
1009    for mount_spec in mounts_iter {
1010        let action = MountAction::from_spec(locked, system_task, pkg_dir_proxy, mount_spec)
1011            .with_source_context(|| format!("creating filesystem from spec: {}", mount_spec))?;
1012        let mount_point = system_task
1013            .lookup_path_from_root(locked, action.path.as_ref())
1014            .with_source_context(|| format!("lookup path from root: {}", action.path))?;
1015        mount_point.mount(WhatToMount::Fs(action.fs), action.flags)?;
1016    }
1017    Ok(())
1018}
1019
1020fn init_remote_block_devices(
1021    locked: &mut Locked<Unlocked>,
1022    system_task: &CurrentTask,
1023) -> Result<(), Error> {
1024    remote_block_device_init(locked, system_task);
1025    let entries = match std::fs::read_dir("/block") {
1026        Ok(entries) => entries,
1027        Err(e) => {
1028            log_warn!("Failed to read block directory: {}", e);
1029            return Ok(());
1030        }
1031    };
1032    for entry in entries {
1033        let entry = entry?;
1034        let path_buf = entry.path();
1035        let path = path_buf.to_str().ok_or_else(|| anyhow!("Invalid block device path"))?;
1036        let (client_end, server_end) = fidl::endpoints::create_endpoints();
1037        match fdio::service_connect(
1038            &format!("{}/fuchsia.storage.block.Block", path),
1039            server_end.into(),
1040        ) {
1041            Ok(()) => (),
1042            Err(e) => {
1043                log_warn!("Failed to connect to block device at {}: {}", path, e);
1044                continue;
1045            }
1046        }
1047        let name = entry.file_name();
1048        let name_str = name.to_str().unwrap();
1049        system_task
1050            .kernel()
1051            .remote_block_device_registry
1052            .create_remote_block_device(locked, system_task.kernel(), &name_str, client_end)
1053            .with_source_context(|| format!("creating remote block device: {name_str}"))?;
1054    }
1055    Ok(())
1056}
1057
1058async fn wait_for_init_file(
1059    startup_file_path: &str,
1060    current_task: &CurrentTask,
1061    init_tid: tid_t,
1062) -> Result<(), Error> {
1063    // TODO(https://fxbug.dev/42178400): Use inotify machinery to wait for the file.
1064    loop {
1065        fasync::Timer::new(fasync::MonotonicDuration::from_millis(100).after_now()).await;
1066
1067        let creds = security::creds_start_internal_operation(current_task);
1068        if let Some(result) = current_task.override_creds(creds, || {
1069            let root = current_task.fs().root();
1070            let mut context = LookupContext::default();
1071
1072            match current_task.lookup_path(
1073                current_task.kernel().kthreads.unlocked_for_async().deref_mut(),
1074                &mut context,
1075                root,
1076                startup_file_path.into(),
1077            ) {
1078                Ok(_) => return Some(Ok(())),
1079                Err(error) if error == ENOENT => {}
1080                Err(error) => return Some(Err(anyhow::Error::from(error))),
1081            };
1082
1083            if current_task.get_task(init_tid).is_err() {
1084                return Some(Err(anyhow!(
1085                    "Init task terminated before startup_file_path was ready"
1086                )));
1087            }
1088
1089            None
1090        }) {
1091            return result;
1092        }
1093    }
1094}
1095
1096async fn serve_runtime_dir(runtime_dir: ServerEnd<fio::DirectoryMarker>) {
1097    let mut fs = fuchsia_component::server::ServiceFs::new();
1098    match create_job_id_vmo() {
1099        Ok(vmo) => {
1100            fs.dir("elf").add_vmo_file_at("job_id", vmo);
1101        }
1102        Err(e) => log_error!(e:%; "failed to create vmo with job id for debuggers"),
1103    }
1104    match fs.serve_connection(runtime_dir) {
1105        Ok(_) => {
1106            fs.add_fidl_service(|job_requests: TaskProviderRequestStream| {
1107                fuchsia_async::Task::local(async move {
1108                    if let Err(e) = serve_task_provider(job_requests).await {
1109                        log_warn!(e:?; "Error serving TaskProvider");
1110                    }
1111                })
1112                .detach();
1113            });
1114            fs.collect::<()>().await;
1115        }
1116        Err(e) => log_error!("Couldn't serve runtime directory: {e:?}"),
1117    }
1118}
1119
1120fn create_job_id_vmo() -> Result<zx::Vmo, Error> {
1121    let job_id = fuchsia_runtime::job_default().koid().context("reading own job koid")?;
1122    let job_id_str = job_id.raw_koid().to_string();
1123    let job_id_vmo = zx::Vmo::create(job_id_str.len() as u64).context("creating job id vmo")?;
1124    job_id_vmo.write(job_id_str.as_bytes(), 0).context("write job id to vmo")?;
1125    Ok(job_id_vmo)
1126}
1127
1128async fn serve_task_provider(mut job_requests: TaskProviderRequestStream) -> Result<(), Error> {
1129    while let Some(request) = job_requests.next().await {
1130        match request.context("getting next TaskProvider request")? {
1131            TaskProviderRequest::GetJob { responder } => {
1132                responder
1133                    .send(
1134                        fuchsia_runtime::job_default()
1135                            .duplicate_handle(zx::Rights::SAME_RIGHTS)
1136                            .map_err(|s| s.into_raw()),
1137                    )
1138                    .context("sending job for runtime dir")?;
1139            }
1140            unknown => bail!("Unknown TaskProvider method {unknown:?}"),
1141        }
1142    }
1143    Ok(())
1144}
1145
1146#[cfg(test)]
1147mod test {
1148    use super::wait_for_init_file;
1149    use fuchsia_async as fasync;
1150    use futures::{SinkExt, StreamExt};
1151    #[allow(deprecated, reason = "pre-existing usage")]
1152    use starnix_core::testing::create_kernel_task_and_unlocked;
1153    use starnix_core::vfs::FdNumber;
1154    use starnix_uapi::CLONE_FS;
1155    use starnix_uapi::file_mode::{AccessCheck, FileMode};
1156    use starnix_uapi::open_flags::OpenFlags;
1157    use starnix_uapi::signals::SIGCHLD;
1158    use starnix_uapi::vfs::ResolveFlags;
1159
1160    #[fuchsia::test]
1161    async fn test_init_file_already_exists() {
1162        #[allow(deprecated, reason = "pre-existing usage")]
1163        let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
1164        let (mut sender, mut receiver) = futures::channel::mpsc::unbounded();
1165
1166        let path = "/path";
1167        current_task
1168            .open_file_at(
1169                locked,
1170                FdNumber::AT_FDCWD,
1171                path.into(),
1172                OpenFlags::CREAT,
1173                FileMode::default(),
1174                ResolveFlags::empty(),
1175                AccessCheck::default(),
1176            )
1177            .expect("Failed to create file");
1178
1179        fasync::Task::local(async move {
1180            wait_for_init_file(path, &current_task, current_task.get_tid())
1181                .await
1182                .expect("failed to wait for file");
1183            sender.send(()).await.expect("failed to send message");
1184        })
1185        .detach();
1186
1187        // Wait for the file creation to have been detected.
1188        assert!(receiver.next().await.is_some());
1189    }
1190
1191    #[fuchsia::test]
1192    async fn test_init_file_wait_required() {
1193        #[allow(deprecated, reason = "pre-existing usage")]
1194        let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
1195        let (mut sender, mut receiver) = futures::channel::mpsc::unbounded();
1196
1197        let init_task = current_task.clone_task_for_test(locked, CLONE_FS as u64, Some(SIGCHLD));
1198        let path = "/path";
1199
1200        let test_init_tid = current_task.get_tid();
1201        fasync::Task::local(async move {
1202            sender.send(()).await.expect("failed to send message");
1203            wait_for_init_file(path, &init_task, test_init_tid)
1204                .await
1205                .expect("failed to wait for file");
1206            sender.send(()).await.expect("failed to send message");
1207        })
1208        .detach();
1209
1210        // Wait for message that file check has started.
1211        assert!(receiver.next().await.is_some());
1212
1213        // Create the file that is being waited on.
1214        current_task
1215            .open_file_at(
1216                locked,
1217                FdNumber::AT_FDCWD,
1218                path.into(),
1219                OpenFlags::CREAT,
1220                FileMode::default(),
1221                ResolveFlags::empty(),
1222                AccessCheck::default(),
1223            )
1224            .expect("Failed to create file");
1225
1226        // Wait for the file creation to be detected.
1227        assert!(receiver.next().await.is_some());
1228    }
1229
1230    #[fuchsia::test]
1231    async fn test_init_exits_before_file_exists() {
1232        #[allow(deprecated, reason = "pre-existing usage")]
1233        let (_kernel, current_task, locked) = create_kernel_task_and_unlocked();
1234        let (mut sender, mut receiver) = futures::channel::mpsc::unbounded();
1235
1236        let init_task = current_task.clone_task_for_test(locked, CLONE_FS as u64, Some(SIGCHLD));
1237        const STARTUP_FILE_PATH: &str = "/path";
1238
1239        let test_init_tid = init_task.get_tid();
1240        fasync::Task::local(async move {
1241            sender.send(()).await.expect("failed to send message");
1242            wait_for_init_file(STARTUP_FILE_PATH, &current_task, test_init_tid)
1243                .await
1244                .expect_err("Did not detect init exit");
1245            sender.send(()).await.expect("failed to send message");
1246        })
1247        .detach();
1248
1249        // Wait for message that file check has started.
1250        assert!(receiver.next().await.is_some());
1251
1252        // Drop the `init_task`.
1253        std::mem::drop(init_task);
1254
1255        // Wait for the init failure to be detected.
1256        assert!(receiver.next().await.is_some());
1257    }
1258}