kernel_manager/
lib.rs

1// Copyright 2023 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
5pub mod kernels;
6pub mod pager;
7pub mod proxy;
8pub mod suspend;
9
10use crate::pager::run_pager;
11use anyhow::{Error, anyhow};
12use fidl::endpoints::{DiscoverableProtocolMarker, Proxy, ServerEnd};
13use fidl::{HandleBased, Peered};
14use fuchsia_component::client as fclient;
15use fuchsia_sync::Mutex;
16use futures::TryStreamExt;
17use kernels::Kernels;
18use log::warn;
19use proxy::ChannelProxy;
20use rand::Rng;
21use std::future::Future;
22use std::sync::Arc;
23use suspend::{
24    ASLEEP_SIGNAL, AWAKE_SIGNAL, SuspendContext, WakeSource, WakeSources, suspend_container,
25};
26use zx::AsHandleRef;
27use {
28    fidl_fuchsia_component as fcomponent, fidl_fuchsia_component_decl as fdecl,
29    fidl_fuchsia_component_runner as frunner, fidl_fuchsia_io as fio,
30    fidl_fuchsia_starnix_container as fstarnix, fidl_fuchsia_starnix_runner as fstarnixrunner,
31    fuchsia_async as fasync,
32};
33
34/// The name of the collection that the starnix_kernel is run in.
35const KERNEL_COLLECTION: &str = "kernels";
36
37/// The name of the protocol the kernel exposes for running containers.
38///
39/// This protocol is actually fuchsia.component.runner.ComponentRunner. We
40/// expose the implementation using this name to avoid confusion with copy
41/// of the fuchsia.component.runner.ComponentRunner protocol used for
42/// running component inside the container.
43const CONTAINER_RUNNER_PROTOCOL: &str = "fuchsia.starnix.container.Runner";
44
45#[allow(dead_code)]
46pub struct StarnixKernel {
47    /// The name of the kernel intsance in the kernels collection.
48    name: String,
49
50    /// The controller used to control the kernel component's lifecycle.
51    ///
52    /// The kernel runs in a "kernels" collection within that realm.
53    controller_proxy: fcomponent::ControllerProxy,
54
55    /// The directory exposed by the Starnix kernel.
56    ///
57    /// This directory can be used to connect to services offered by the kernel.
58    exposed_dir: fio::DirectoryProxy,
59
60    /// An opaque token representing the container component.
61    component_instance: zx::Event,
62
63    /// The job the kernel lives under.
64    job: Arc<zx::Job>,
65
66    /// The currently active wake lease for the container.
67    wake_lease: Mutex<Option<zx::EventPair>>,
68}
69
70impl StarnixKernel {
71    /// Creates a new instance of `starnix_kernel`.
72    ///
73    /// This is done by creating a new child in the `kernels` collection.
74    ///
75    /// Returns the kernel and a future that will resolve when the kernel has stopped.
76    pub async fn create(
77        realm: fcomponent::RealmProxy,
78        kernel_url: &str,
79        start_info: frunner::ComponentStartInfo,
80        controller: ServerEnd<frunner::ComponentControllerMarker>,
81    ) -> Result<(Self, impl Future<Output = ()>), Error> {
82        let kernel_name = generate_kernel_name(&start_info)?;
83        let component_instance = start_info
84            .component_instance
85            .as_ref()
86            .ok_or_else(|| {
87                anyhow::anyhow!("expected to find component_instance in ComponentStartInfo")
88            })?
89            .duplicate_handle(zx::Rights::SAME_RIGHTS)?;
90
91        // Create the `starnix_kernel`.
92        let (controller_proxy, controller_server_end) = fidl::endpoints::create_proxy();
93        realm
94            .create_child(
95                &fdecl::CollectionRef { name: KERNEL_COLLECTION.into() },
96                &fdecl::Child {
97                    name: Some(kernel_name.clone()),
98                    url: Some(kernel_url.to_string()),
99                    startup: Some(fdecl::StartupMode::Lazy),
100                    ..Default::default()
101                },
102                fcomponent::CreateChildArgs {
103                    controller: Some(controller_server_end),
104                    ..Default::default()
105                },
106            )
107            .await?
108            .map_err(|e| anyhow::anyhow!("failed to create kernel: {:?}", e))?;
109
110        // Start the kernel to obtain an `ExecutionController`.
111        let (execution_controller_proxy, execution_controller_server_end) =
112            fidl::endpoints::create_proxy();
113        controller_proxy
114            .start(fcomponent::StartChildArgs::default(), execution_controller_server_end)
115            .await?
116            .map_err(|e| anyhow::anyhow!("failed to start kernel: {:?}", e))?;
117
118        let exposed_dir = open_exposed_directory(&realm, &kernel_name, KERNEL_COLLECTION).await?;
119        let container_runner = fclient::connect_to_named_protocol_at_dir_root::<
120            frunner::ComponentRunnerMarker,
121        >(&exposed_dir, CONTAINER_RUNNER_PROTOCOL)?;
122
123        // Actually start the container.
124        container_runner.start(start_info, controller)?;
125
126        // Ask the kernel for its job.
127        let container_controller =
128            fclient::connect_to_protocol_at_dir_root::<fstarnix::ControllerMarker>(&exposed_dir)?;
129        let fstarnix::ControllerGetJobHandleResponse { job, .. } =
130            container_controller.get_job_handle().await?;
131        let Some(job) = job else {
132            anyhow::bail!("expected to find job in ControllerGetJobHandleResponse");
133        };
134
135        let kernel = Self {
136            name: kernel_name,
137            controller_proxy,
138            exposed_dir,
139            component_instance,
140            job: Arc::new(job),
141            wake_lease: Default::default(),
142        };
143        let on_stop = async move {
144            _ = execution_controller_proxy.into_channel().unwrap().on_closed().await;
145        };
146        Ok((kernel, on_stop))
147    }
148
149    /// Gets the opaque token representing the container component.
150    pub fn component_instance(&self) -> &zx::Event {
151        &self.component_instance
152    }
153
154    /// Gets the job the kernel lives under.
155    pub fn job(&self) -> &Arc<zx::Job> {
156        &self.job
157    }
158
159    /// Connect to the specified protocol exposed by the kernel.
160    pub fn connect_to_protocol<P: DiscoverableProtocolMarker>(&self) -> Result<P::Proxy, Error> {
161        fclient::connect_to_protocol_at_dir_root::<P>(&self.exposed_dir)
162    }
163
164    /// Destroys the Starnix kernel that is running the given test.
165    pub async fn destroy(self) -> Result<(), Error> {
166        self.controller_proxy
167            .destroy()
168            .await?
169            .map_err(|e| anyhow!("kernel component destruction failed: {e:?}"))?;
170        let mut event_stream = self.controller_proxy.take_event_stream();
171        loop {
172            match event_stream.try_next().await {
173                Ok(Some(_)) => continue,
174                Ok(None) => return Ok(()),
175                Err(e) => return Err(e.into()),
176            }
177        }
178    }
179}
180
181/// Generate a random name for the kernel.
182///
183/// We used to include some human-readable parts in the name, but people were
184/// tempted to make them load-bearing. We now just generate 7 random alphanumeric
185/// characters.
186fn generate_kernel_name(_start_info: &frunner::ComponentStartInfo) -> Result<String, Error> {
187    let random_id: String =
188        rand::rng().sample_iter(&rand::distr::Alphanumeric).take(7).map(char::from).collect();
189    Ok(random_id)
190}
191
192async fn open_exposed_directory(
193    realm: &fcomponent::RealmProxy,
194    child_name: &str,
195    collection_name: &str,
196) -> Result<fio::DirectoryProxy, Error> {
197    let (directory_proxy, server_end) = fidl::endpoints::create_proxy::<fio::DirectoryMarker>();
198    realm
199        .open_exposed_dir(
200            &fdecl::ChildRef { name: child_name.into(), collection: Some(collection_name.into()) },
201            server_end,
202        )
203        .await?
204        .map_err(|e| {
205            anyhow!(
206                "failed to bind to child {} in collection {:?}: {:?}",
207                child_name,
208                collection_name,
209                e
210            )
211        })?;
212    Ok(directory_proxy)
213}
214
215pub async fn serve_starnix_manager(
216    mut stream: fstarnixrunner::ManagerRequestStream,
217    suspend_context: Arc<SuspendContext>,
218    kernels: &Kernels,
219    sender: &async_channel::Sender<(ChannelProxy, Arc<Mutex<WakeSources>>)>,
220) -> Result<(), Error> {
221    while let Some(event) = stream.try_next().await? {
222        match event {
223            fstarnixrunner::ManagerRequest::SuspendContainer { payload, responder, .. } => {
224                let response = suspend_container(payload, &suspend_context, kernels).await?;
225                if let Err(e) = match response {
226                    Ok(o) => responder.send(Ok(&o)),
227                    Err(e) => responder.send(Err(e)),
228                } {
229                    warn!("error replying to suspend request: {e}");
230                }
231            }
232            fstarnixrunner::ManagerRequest::ProxyWakeChannel { payload, .. } => {
233                let fstarnixrunner::ManagerProxyWakeChannelRequest {
234                    // TODO: Handle more than one container.
235                    container_job: Some(_container_job),
236                    remote_channel: Some(remote_channel),
237                    container_channel: Some(container_channel),
238                    name: Some(name),
239                    counter: Some(message_counter),
240                    ..
241                } = payload
242                else {
243                    continue;
244                };
245
246                suspend_context.wake_sources.lock().insert(
247                    message_counter.get_koid().unwrap(),
248                    WakeSource::from_counter(
249                        message_counter.duplicate_handle(zx::Rights::SAME_RIGHTS)?,
250                        name.clone(),
251                    ),
252                );
253
254                let proxy =
255                    ChannelProxy { container_channel, remote_channel, message_counter, name };
256
257                sender.try_send((proxy, suspend_context.wake_sources.clone())).unwrap();
258            }
259            fstarnixrunner::ManagerRequest::RegisterWakeWatcher { payload, responder } => {
260                if let Some(watcher) = payload.watcher {
261                    let (clear_mask, set_mask) = (ASLEEP_SIGNAL, AWAKE_SIGNAL);
262                    watcher.signal_peer(clear_mask, set_mask)?;
263
264                    suspend_context.wake_watchers.lock().push(watcher);
265                }
266                if let Err(e) = responder.send() {
267                    warn!("error registering power watcher: {e}");
268                }
269            }
270            fstarnixrunner::ManagerRequest::AddWakeSource { payload, .. } => {
271                let fstarnixrunner::ManagerAddWakeSourceRequest {
272                    // TODO: Handle more than one container.
273                    container_job: Some(_container_job),
274                    name: Some(name),
275                    handle: Some(handle),
276                    signals: Some(signals),
277                    ..
278                } = payload
279                else {
280                    continue;
281                };
282                suspend_context.wake_sources.lock().insert(
283                    handle.get_koid().unwrap(),
284                    WakeSource::from_handle(
285                        handle,
286                        name.clone(),
287                        zx::Signals::from_bits_truncate(signals),
288                    ),
289                );
290            }
291            fstarnixrunner::ManagerRequest::RemoveWakeSource { payload, .. } => {
292                let fstarnixrunner::ManagerRemoveWakeSourceRequest {
293                    // TODO: Handle more than one container.
294                    container_job: Some(_container_job),
295                    handle: Some(handle),
296                    ..
297                } = payload
298                else {
299                    continue;
300                };
301
302                suspend_context.wake_sources.lock().remove(&handle.get_koid().unwrap());
303            }
304            fstarnixrunner::ManagerRequest::CreatePager { payload, .. } => {
305                std::thread::spawn(|| {
306                    fasync::LocalExecutor::default().run_singlethreaded(run_pager(payload));
307                });
308            }
309            _ => {}
310        }
311    }
312    Ok(())
313}