1pub mod kernels;
6pub mod proxy;
7pub mod suspend;
8
9use anyhow::{anyhow, Error};
10use fidl::endpoints::{DiscoverableProtocolMarker, Proxy, ServerEnd};
11use fidl::{HandleBased, Peered};
12use fuchsia_component::client as fclient;
13use fuchsia_sync::Mutex;
14use futures::TryStreamExt;
15use kernels::Kernels;
16use log::warn;
17use proxy::ChannelProxy;
18use rand::Rng;
19use std::future::Future;
20use std::sync::Arc;
21use suspend::{
22 suspend_container, SuspendContext, WakeSource, WakeSources, ASLEEP_SIGNAL, AWAKE_SIGNAL,
23};
24use zx::AsHandleRef;
25use {
26 fidl_fuchsia_component as fcomponent, fidl_fuchsia_component_decl as fdecl,
27 fidl_fuchsia_component_runner as frunner, fidl_fuchsia_io as fio,
28 fidl_fuchsia_starnix_container as fstarnix, fidl_fuchsia_starnix_runner as fstarnixrunner,
29};
30
31const KERNEL_COLLECTION: &str = "kernels";
33
34const CONTAINER_RUNNER_PROTOCOL: &str = "fuchsia.starnix.container.Runner";
41
42#[allow(dead_code)]
43pub struct StarnixKernel {
44 name: String,
46
47 controller_proxy: fcomponent::ControllerProxy,
51
52 exposed_dir: fio::DirectoryProxy,
56
57 component_instance: zx::Event,
59
60 job: Arc<zx::Job>,
62
63 wake_lease: Mutex<Option<zx::EventPair>>,
65}
66
67impl StarnixKernel {
68 pub async fn create(
74 realm: fcomponent::RealmProxy,
75 kernel_url: &str,
76 start_info: frunner::ComponentStartInfo,
77 controller: ServerEnd<frunner::ComponentControllerMarker>,
78 ) -> Result<(Self, impl Future<Output = ()>), Error> {
79 let kernel_name = generate_kernel_name(&start_info)?;
80 let component_instance = start_info
81 .component_instance
82 .as_ref()
83 .ok_or_else(|| {
84 anyhow::anyhow!("expected to find component_instance in ComponentStartInfo")
85 })?
86 .duplicate_handle(zx::Rights::SAME_RIGHTS)?;
87
88 let (controller_proxy, controller_server_end) = fidl::endpoints::create_proxy();
90 realm
91 .create_child(
92 &fdecl::CollectionRef { name: KERNEL_COLLECTION.into() },
93 &fdecl::Child {
94 name: Some(kernel_name.clone()),
95 url: Some(kernel_url.to_string()),
96 startup: Some(fdecl::StartupMode::Lazy),
97 ..Default::default()
98 },
99 fcomponent::CreateChildArgs {
100 controller: Some(controller_server_end),
101 ..Default::default()
102 },
103 )
104 .await?
105 .map_err(|e| anyhow::anyhow!("failed to create kernel: {:?}", e))?;
106
107 let (execution_controller_proxy, execution_controller_server_end) =
109 fidl::endpoints::create_proxy();
110 controller_proxy
111 .start(fcomponent::StartChildArgs::default(), execution_controller_server_end)
112 .await?
113 .map_err(|e| anyhow::anyhow!("failed to start kernel: {:?}", e))?;
114
115 let exposed_dir = open_exposed_directory(&realm, &kernel_name, KERNEL_COLLECTION).await?;
116 let container_runner = fclient::connect_to_named_protocol_at_dir_root::<
117 frunner::ComponentRunnerMarker,
118 >(&exposed_dir, CONTAINER_RUNNER_PROTOCOL)?;
119
120 container_runner.start(start_info, controller)?;
122
123 let container_controller =
125 fclient::connect_to_protocol_at_dir_root::<fstarnix::ControllerMarker>(&exposed_dir)?;
126 let fstarnix::ControllerGetJobHandleResponse { job, .. } =
127 container_controller.get_job_handle().await?;
128 let Some(job) = job else {
129 anyhow::bail!("expected to find job in ControllerGetJobHandleResponse");
130 };
131
132 let kernel = Self {
133 name: kernel_name,
134 controller_proxy,
135 exposed_dir,
136 component_instance,
137 job: Arc::new(job),
138 wake_lease: Default::default(),
139 };
140 let on_stop = async move {
141 _ = execution_controller_proxy.into_channel().unwrap().on_closed().await;
142 };
143 Ok((kernel, on_stop))
144 }
145
146 pub fn component_instance(&self) -> &zx::Event {
148 &self.component_instance
149 }
150
151 pub fn job(&self) -> &Arc<zx::Job> {
153 &self.job
154 }
155
156 pub fn connect_to_protocol<P: DiscoverableProtocolMarker>(&self) -> Result<P::Proxy, Error> {
158 fclient::connect_to_protocol_at_dir_root::<P>(&self.exposed_dir)
159 }
160
161 pub async fn destroy(self) -> Result<(), Error> {
163 self.controller_proxy
164 .destroy()
165 .await?
166 .map_err(|e| anyhow!("kernel component destruction failed: {e:?}"))?;
167 let mut event_stream = self.controller_proxy.take_event_stream();
168 loop {
169 match event_stream.try_next().await {
170 Ok(Some(_)) => continue,
171 Ok(None) => return Ok(()),
172 Err(e) => return Err(e.into()),
173 }
174 }
175 }
176}
177
178fn generate_kernel_name(_start_info: &frunner::ComponentStartInfo) -> Result<String, Error> {
184 let random_id: String = rand::thread_rng()
185 .sample_iter(&rand::distributions::Alphanumeric)
186 .take(7)
187 .map(char::from)
188 .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::new(
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 _ => {}
271 }
272 }
273 Ok(())
274}