1pub 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
34const KERNEL_COLLECTION: &str = "kernels";
36
37const CONTAINER_RUNNER_PROTOCOL: &str = "fuchsia.starnix.container.Runner";
44
45#[allow(dead_code)]
46pub struct StarnixKernel {
47 name: String,
49
50 controller_proxy: fcomponent::ControllerProxy,
54
55 exposed_dir: fio::DirectoryProxy,
59
60 component_instance: zx::Event,
62
63 job: Arc<zx::Job>,
65
66 wake_lease: Mutex<Option<zx::EventPair>>,
68}
69
70impl StarnixKernel {
71 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 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 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 container_runner.start(start_info, controller)?;
125
126 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 pub fn component_instance(&self) -> &zx::Event {
151 &self.component_instance
152 }
153
154 pub fn job(&self) -> &Arc<zx::Job> {
156 &self.job
157 }
158
159 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 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
181fn 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 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 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 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}