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